-
onInputSearchKeyword(e.target.value)}
- />
-
- {marketPluginList.map((vo, index) => {
- return (
-
-
+ for (let i = 0; i < totalPlugins; i++) {
+ allPlugins.push(new PluginMarketCardVO({
+ pluginId: `plugin-${i}`,
+ description: `这是插件 ${i} 的描述,包含一些详细信息`,
+ name: `插件 ${i}`,
+ author: `/author-${i % 5}`, // 模拟5个不同的作者
+ version: `0.${i % 10}`,
+ githubURL: `https://github.com/author-${i % 5}/plugin-${i}`,
+ starCount: 10 + Math.floor(Math.random() * 100)
+ }))
+ }
+
+ // 根据关键词过滤
+ const filtered = keyword
+ ? allPlugins.filter(p =>
+ p.name.toLowerCase().includes(keyword.toLowerCase()) ||
+ p.description.toLowerCase().includes(keyword.toLowerCase()))
+ : allPlugins
+
+ // 分页处理
+ const start = (page - 1) * pageSize
+ const end = start + pageSize
+ const paginatedData = filtered.slice(start, end)
+
+ return {
+ data: paginatedData,
+ total: filtered.length
+ }
+ }
+
+ function onInputSearchKeyword(keyword: string) {
+ setSearchKeyword(keyword)
+ setCurrentPage(1) // 搜索时重置为第一页
+ debounceSearch(keyword)
+ }
+
+ const debounceSearch = useCallback(
+ debounce((keyword: string) => {
+ fetchPlugins(keyword, 1)
+ }, 500), []
+ )
+
+ function handlePageChange(page: number) {
+ setCurrentPage(page)
+ }
+
+ return (
+
+
onInputSearchKeyword(e.target.value)}
+ />
+
+ {loading ? (
+
加载中...
+ ) : marketPluginList.length === 0 ? (
+
没有找到匹配的插件
+ ) : (
+ marketPluginList.map((vo, index) => (
+
+ ))
+ )}
- );
- })}
-
-
{
- setNowPage(pageNumber);
- getPluginList(pageNumber);
- }}
- />
-
- );
+ {totalItems > 0 && (
+
+ )}
+
+ )
}
diff --git a/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts b/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts
index 6af3f199..9d508066 100644
--- a/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts
+++ b/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO.ts
@@ -5,6 +5,7 @@ export interface IPluginMarketCardVO {
description: string,
starCount: number,
githubURL: string,
+ version: string,
}
export class PluginMarketCardVO implements IPluginMarketCardVO {
@@ -14,6 +15,7 @@ export class PluginMarketCardVO implements IPluginMarketCardVO {
author: string;
githubURL: string;
starCount: number;
+ version: string;
constructor(prop: IPluginMarketCardVO) {
this.description = prop.description
@@ -22,5 +24,6 @@ export class PluginMarketCardVO implements IPluginMarketCardVO {
this.githubURL = prop.githubURL
this.starCount = prop.starCount
this.pluginId = prop.pluginId
+ this.version = prop.version
}
}
diff --git a/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css b/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css
index f611f335..33297b09 100644
--- a/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css
+++ b/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css
@@ -1,6 +1,4 @@
.cardContainer {
- width: 360px;
- height: 140px;
box-sizing: border-box;
background-color: #FFF;
border-radius: 9px;
@@ -74,4 +72,4 @@
align-self: center;
justify-content: space-between;
}
-}
+}
\ No newline at end of file
diff --git a/web/src/app/home/plugins/plugins.module.css b/web/src/app/home/plugins/plugins.module.css
index 1393c15b..24688273 100644
--- a/web/src/app/home/plugins/plugins.module.css
+++ b/web/src/app/home/plugins/plugins.module.css
@@ -1,8 +1,8 @@
.pageContainer {
width: 100%;
- height: calc(100% - 30px);
}
+
.marketComponentBody {
width: 100%;
height: calc(100% - 60px);
@@ -11,17 +11,13 @@
.pluginListContainer {
align-self: flex-start;
justify-self: flex-start;
- width: calc(100% - 60px);
- height: 100%;
- max-height: 100%;
- margin: auto;
+ margin: auto;
display: grid;
grid-template-rows: repeat(auto-fill, minmax(160px, 1fr));
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 15px;
- justify-items: center;
+ /* justify-items: center; */
align-items: center;
- overflow-y: scroll;
}
.modalTitle {
@@ -35,4 +31,4 @@
display: flex;
flex-direction: column;
justify-content: space-around;
-}
+}
\ No newline at end of file
diff --git a/web/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx b/web/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx
index 3e33d712..f1a280fc 100644
--- a/web/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx
+++ b/web/src/app/infra/basic-component/create-card-component/CreateCardComponent.tsx
@@ -1,12 +1,10 @@
import styles from "./createCartComponent.module.css";
export default function CreateCardComponent({
- width,
height,
plusSize,
onClick,
}: {
- width: number;
height: number;
plusSize: number;
onClick: () => void
@@ -15,7 +13,7 @@ export default function CreateCardComponent({
-
-
- {/* Ant Design 图标,可以换成 Langbot 的 Logo */}
-
-
-
+
+
+
+
+
+ {/* Ant Design 图标,可以换成 Langbot 的 Logo */}
+
+
+
-
-
- 404
-
-
- 页面不存在
-
-
- 您要查找的页面似乎不存在。请检查您输入的 URL 是否正确,或者返回首页。
-
-
+
+
+ 404
+
+
+ 页面不存在
+
+
+ 您要查找的页面似乎不存在。请检查您输入的 URL 是否正确,或者返回首页。
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
+
+
);
}
From 4b5ac6ad03604f523f58ace02cf16d12fdc2f1be Mon Sep 17 00:00:00 2001
From: Chris <1637083533@qq.com>
Date: Mon, 28 Apr 2025 23:14:35 +0800
Subject: [PATCH 2/8] http
---
web/src/app/infra/http/HttpClient.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/web/src/app/infra/http/HttpClient.ts b/web/src/app/infra/http/HttpClient.ts
index 192566a0..a8bb53cc 100644
--- a/web/src/app/infra/http/HttpClient.ts
+++ b/web/src/app/infra/http/HttpClient.ts
@@ -129,9 +129,9 @@ class HttpClient {
const errMessage = data?.message || error.message;
switch (status) {
- // case 401:
- // window.location.href = "/login";
- // break;
+ case 401:
+ window.location.href = "/login";
+ break;
case 403:
console.error("Permission denied:", errMessage);
break;
From 9d724dbb8d28bd8eb29e203544ea3d79b28f5455 Mon Sep 17 00:00:00 2001
From: chris <1637083533@qq.com>
Date: Tue, 29 Apr 2025 14:58:17 +0800
Subject: [PATCH 3/8] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=93=E5=BA=93?=
=?UTF-8?q?=E5=86=B2=E7=AA=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../components/home-sidebar/HomeSidebar.tsx | 118 ++++++++--------
web/src/app/home/layout.tsx | 4 +-
.../PluginInstalledComponent.tsx | 1 +
.../plugin-card/PluginCardComponent.tsx | 22 ++-
.../plugin-market/PluginMarketComponent.tsx | 126 ++++++++----------
5 files changed, 131 insertions(+), 140 deletions(-)
diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx
index 56a6d6f5..7a950e91 100644
--- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx
+++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx
@@ -1,81 +1,71 @@
-"use client"
+"use client";
-import styles from "./HomeSidebar.module.css"
+import styles from "./HomeSidebar.module.css";
import { useEffect, useState } from "react";
-import { SidebarChild, SidebarChildVO } from "@/app/home/components/home-sidebar/HomeSidebarChild";
-import { useRouter, usePathname, useSearchParams } from "next/navigation";
+import {
+ SidebarChild,
+ SidebarChildVO
+} from "@/app/home/components/home-sidebar/HomeSidebarChild";
+import { useRouter, usePathname } from "next/navigation";
import { sidebarConfigList } from "@/app/home/components/home-sidebar/sidbarConfigList";
// TODO 侧边导航栏要加动画
export default function HomeSidebar({
- onSelectedChange
+ onSelectedChangeAction
}: {
- onSelectedChange: (sidebarChild: SidebarChildVO) => void
+ onSelectedChangeAction: (sidebarChild: SidebarChildVO) => void;
}) {
- // 路由相关
- const router = useRouter()
- const pathname = usePathname();
- const searchParams = useSearchParams();
- // 路由被动变化时处理
- useEffect(() => {
- handleRouteChange(pathname)
- }, [pathname, searchParams]);
+ // 路由相关
+ const router = useRouter();
+ const pathname = usePathname();
+ // 路由被动变化时处理
+ useEffect(() => {
+ handleRouteChange(pathname);
+ }, [pathname]);
- const [selectedChild, setSelectedChild] = useState
(sidebarConfigList[0])
+ const [selectedChild, setSelectedChild] = useState(
+ sidebarConfigList[0]
+ );
- useEffect(() => {
- console.log('HomeSidebar挂载完成');
- initSelect()
- return () => console.log('HomeSidebar卸载');
- }, []);
+ useEffect(() => {
+ console.log("HomeSidebar挂载完成");
+ initSelect();
+ return () => console.log("HomeSidebar卸载");
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+ function handleChildClick(child: SidebarChildVO) {
+ setSelectedChild(child);
+ handleRoute(child);
+ onSelectedChangeAction(child);
+ }
+ function initSelect() {
+ handleChildClick(sidebarConfigList[0]);
+ }
- function handleChildClick(child: SidebarChildVO) {
- setSelectedChild(child)
- handleRoute(child)
- onSelectedChange(child)
- }
-
- function initSelect() {
- // 根据当前URL路径选择相应的菜单项,而不是总是使用第一个菜单项
- const currentPath = pathname;
- const matchedChild = sidebarConfigList.find(child => child.route === currentPath);
-
- if (matchedChild) {
- // 如果找到匹配的菜单项,则选择它
- setSelectedChild(matchedChild);
- onSelectedChange(matchedChild);
- } else {
- // 如果没有匹配项,则回退到默认选择第一个菜单项
- handleChildClick(sidebarConfigList[0]);
- }
- }
-
- function handleRoute(child: SidebarChildVO) {
- console.log(child)
- router.push(`${child.route}`)
- }
-
- function handleRouteChange(pathname: string) {
- // TODO 这段逻辑并不好,未来router封装好后改掉
- // 判断在home下,并且路由更改的是自己的路由子组件则更新UI
- const routeList = pathname.split('/')
- if (
- routeList[1] === "home" &&
- sidebarConfigList.find(childConfig =>
- childConfig.route === pathname
- )
- ) {
- console.log("find success")
- const routeSelectChild = sidebarConfigList.find(childConfig =>
- childConfig.route === pathname
- )
- if (routeSelectChild) {
- setSelectedChild(routeSelectChild)
- }
- }
+ function handleRoute(child: SidebarChildVO) {
+ console.log(child);
+ router.push(`${child.route}`);
+ }
+
+ function handleRouteChange(pathname: string) {
+ // TODO 这段逻辑并不好,未来router封装好后改掉
+ // 判断在home下,并且路由更改的是自己的路由子组件则更新UI
+ const routeList = pathname.split("/");
+ if (
+ routeList[1] === "home" &&
+ sidebarConfigList.find((childConfig) => childConfig.route === pathname)
+ ) {
+ console.log("find success");
+ const routeSelectChild = sidebarConfigList.find(
+ (childConfig) => childConfig.route === pathname
+ );
+ if (routeSelectChild) {
+ setSelectedChild(routeSelectChild);
+ }
}
+ }
return (
diff --git a/web/src/app/home/layout.tsx b/web/src/app/home/layout.tsx
index 8ac4efb4..c765aef0 100644
--- a/web/src/app/home/layout.tsx
+++ b/web/src/app/home/layout.tsx
@@ -18,7 +18,7 @@ export default function HomeLayout({
}>) {
const router = useRouter();
const [title, setTitle] = useState("")
- const onSelectedChange = (child: SidebarChildVO) => {
+ const onSelectedChangeAction = (child: SidebarChildVO) => {
setTitle(child.name)
}
@@ -28,7 +28,7 @@ export default function HomeLayout({
{/* HomeSidebar 为侧边栏 */}
diff --git a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx
index 2897cef8..be770733 100644
--- a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx
+++ b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx
@@ -16,6 +16,7 @@ export default function PluginInstalledComponent() {
useEffect(() => {
initData()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
function initData() {
diff --git a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx
index 267bb3b7..9efbbe9e 100644
--- a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx
+++ b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx
@@ -1,13 +1,33 @@
import styles from "./pluginCard.module.css"
import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO";
import { GithubOutlined, LinkOutlined, ToolOutlined } from '@ant-design/icons';
-import { Tag } from 'antd'
+import { Switch, Tag } from 'antd'
+import { useState } from "react";
+import { httpClient } from "@/app/infra/http/HttpClient";
export default function PluginCardComponent({
cardVO
}: {
cardVO: PluginCardVO
}) {
+ const [initialized, setInitialized] = useState(cardVO.isInitialized);
+ const [switchEnable, setSwitchEnable] = useState(true);
+
+ function handleEnable() {
+ setSwitchEnable(false);
+ httpClient
+ .togglePlugin(cardVO.author, cardVO.name, !initialized)
+ .then(() => {
+ setInitialized(!initialized);
+ })
+ .catch((err) => {
+ console.log("error: ", err);
+ })
+ .finally(() => {
+ setSwitchEnable(true);
+ });
+ }
+
return (
{/* header */}
diff --git a/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx b/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx
index 793db49c..9e84c0ff 100644
--- a/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx
+++ b/web/src/app/home/plugins/plugin-market/PluginMarketComponent.tsx
@@ -1,90 +1,70 @@
-"use client"
+"use client";
-import { useCallback, useEffect, useState } from "react";
+import { useEffect, useState } from "react";
import styles from "@/app/home/plugins/plugins.module.css";
import { PluginMarketCardVO } from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO";
import PluginMarketCardComponent from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent";
import { Input, Pagination } from "antd";
-import { debounce } from "lodash"
+import { spaceClient } from "@/app/infra/http/HttpClient";
export default function PluginMarketComponent() {
- const [marketPluginList, setMarketPluginList] = useState
([])
- const [searchKeyword, setSearchKeyword] = useState("")
- const [currentPage, setCurrentPage] = useState(1)
- const [totalItems, setTotalItems] = useState(0)
- const [loading, setLoading] = useState(false)
- const pageSize = 10 // 每页显示的项目数量
+ const [marketPluginList, setMarketPluginList] = useState<
+ PluginMarketCardVO[]
+ >([]);
+ const [totalCount, setTotalCount] = useState(0);
+ const [nowPage, setNowPage] = useState(1);
+ const [searchKeyword, setSearchKeyword] = useState("");
+ const [loading, setLoading] = useState(false);
+ const pageSize = 10;
useEffect(() => {
- fetchPlugins(searchKeyword, currentPage)
- }, [currentPage])
+ initData();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
- // 获取插件列表,整合了搜索和分页功能
- async function fetchPlugins(keyword: string = "", page: number = 1): Promise {
- setLoading(true)
- try {
- // 实际应用中,这里应该调用API获取数据
- const result = await mockFetchPlugins(keyword, page, pageSize)
- setMarketPluginList(result.data)
- setTotalItems(result.total)
- } finally {
- setLoading(false)
- }
- }
-
- // 模拟从API获取数据
- async function mockFetchPlugins(keyword: string, page: number, pageSize: number): Promise<{ data: PluginMarketCardVO[], total: number }> {
- // 模拟API延迟
- await new Promise(resolve => setTimeout(resolve, 300))
-
- // 创建模拟数据
- const allPlugins: PluginMarketCardVO[] = []
- const totalPlugins = 50 // 模拟总数据量
-
- for (let i = 0; i < totalPlugins; i++) {
- allPlugins.push(new PluginMarketCardVO({
- pluginId: `plugin-${i}`,
- description: `这是插件 ${i} 的描述,包含一些详细信息`,
- name: `插件 ${i}`,
- author: `/author-${i % 5}`, // 模拟5个不同的作者
- version: `0.${i % 10}`,
- githubURL: `https://github.com/author-${i % 5}/plugin-${i}`,
- starCount: 10 + Math.floor(Math.random() * 100)
- }))
- }
-
- // 根据关键词过滤
- const filtered = keyword
- ? allPlugins.filter(p =>
- p.name.toLowerCase().includes(keyword.toLowerCase()) ||
- p.description.toLowerCase().includes(keyword.toLowerCase()))
- : allPlugins
-
- // 分页处理
- const start = (page - 1) * pageSize
- const end = start + pageSize
- const paginatedData = filtered.slice(start, end)
-
- return {
- data: paginatedData,
- total: filtered.length
- }
+ function initData() {
+ getPluginList();
}
function onInputSearchKeyword(keyword: string) {
- setSearchKeyword(keyword)
- setCurrentPage(1) // 搜索时重置为第一页
- debounceSearch(keyword)
+ // 这里记得加防抖,暂时没加
+ setSearchKeyword(keyword);
+ setNowPage(1);
+ getPluginList(1, keyword);
}
- const debounceSearch = useCallback(
- debounce((keyword: string) => {
- fetchPlugins(keyword, 1)
- }, 500), []
- )
+ function getPluginList(
+ page: number = nowPage,
+ keyword: string = searchKeyword
+ ) {
+ setLoading(true);
+ spaceClient.getMarketPlugins(page, pageSize, keyword).then((res) => {
+ setMarketPluginList(
+ res.plugins.map(
+ (marketPlugin) =>
+ new PluginMarketCardVO({
+ author: marketPlugin.author,
+ description: marketPlugin.description,
+ githubURL: marketPlugin.repository,
+ name: marketPlugin.name,
+ pluginId: String(marketPlugin.ID),
+ starCount: marketPlugin.stars,
+ version: "version" in marketPlugin ? String(marketPlugin.version) : "1.0.0", // Default version if not provided
+ })
+ )
+ );
+ setTotalCount(res.total);
+ setLoading(false);
+ console.log("market plugins:", res);
+ }).catch(error => {
+ console.error("获取插件列表失败:", error);
+ setLoading(false);
+ });
+ }
function handlePageChange(page: number) {
- setCurrentPage(page)
+ setNowPage(page);
+ getPluginList(page);
}
return (
@@ -111,11 +91,11 @@ export default function PluginMarketComponent() {
))
)}
- {totalItems > 0 && (
+ {totalCount > 0 && (
Date: Tue, 29 Apr 2025 15:05:15 +0800
Subject: [PATCH 4/8] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=93=E5=BA=93?=
=?UTF-8?q?=E5=86=B2=E7=AA=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../home/plugins/plugin-installed/PluginInstalledComponent.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx
index be770733..39c2be86 100644
--- a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx
+++ b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx
@@ -15,7 +15,7 @@ export default function PluginInstalledComponent() {
useEffect(() => {
- initData()
+ initData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
From 44b005ffdd53acd8c52dd8b0c63c9b07ab2649d9 Mon Sep 17 00:00:00 2001
From: chris <1637083533@qq.com>
Date: Tue, 29 Apr 2025 15:32:06 +0800
Subject: [PATCH 5/8] =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=86=B2=E7=AA=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
web/src/app/home/bots/page.tsx | 280 +++++++++---------
.../PluginInstalledComponent.tsx | 264 +++--------------
web/src/app/infra/http/HttpClient.ts | 26 +-
3 files changed, 200 insertions(+), 370 deletions(-)
diff --git a/web/src/app/home/bots/page.tsx b/web/src/app/home/bots/page.tsx
index a3570eaf..c2760b01 100644
--- a/web/src/app/home/bots/page.tsx
+++ b/web/src/app/home/bots/page.tsx
@@ -1,4 +1,4 @@
-"use client"
+"use client";
import { useEffect, useState } from "react";
import styles from "./botConfig.module.css";
@@ -8,155 +8,161 @@ import { BotCardVO } from "@/app/home/bots/components/bot-card/BotCardVO";
import { Modal, notification, Spin } from "antd";
import BotForm from "@/app/home/bots/components/bot-form/BotForm";
import BotCard from "@/app/home/bots/components/bot-card/BotCard";
-import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent"
+import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent";
import { httpClient } from "@/app/infra/http/HttpClient";
import { Bot } from "@/app/infra/api/api-types";
export default function BotConfigPage() {
- const router = useRouter();
- const [pageShowRule, setPageShowRule] = useState(BotConfigPageShowRule.NO_BOT)
- const [modalOpen, setModalOpen] = useState(false);
- const [botList, setBotList] = useState([])
- const [isEditForm, setIsEditForm] = useState(false)
- const [nowSelectedBotCard, setNowSelectedBotCard] = useState()
- const [isLoading, setIsLoading] = useState(false)
+ const router = useRouter();
+ const [pageShowRule, setPageShowRule] = useState(
+ BotConfigPageShowRule.NO_BOT
+ );
+ const [modalOpen, setModalOpen] = useState(false);
+ const [botList, setBotList] = useState([]);
+ const [isEditForm, setIsEditForm] = useState(false);
+ const [nowSelectedBotCard, setNowSelectedBotCard] = useState();
+ const [isLoading, setIsLoading] = useState(false);
+ useEffect(() => {
+ // TODO:补齐加载转圈逻辑
+ setIsLoading(true);
+ checkHasLLM().then((hasLLM) => {
+ if (hasLLM) {
+ getBotList();
+ } else {
+ setPageShowRule(BotConfigPageShowRule.NO_LLM);
+ setIsLoading(false);
+ }
+ });
+ }, []);
- useEffect(() => {
- // TODO:补齐加载转圈逻辑
- setIsLoading(true)
- checkHasLLM().then((hasLLM) => {
- if (hasLLM) {
- getBotList()
- } else {
- setPageShowRule(BotConfigPageShowRule.NO_LLM)
- setIsLoading(false)
- }
- })
- }, [])
+ async function checkHasLLM(): Promise {
+ // NOT IMPL
+ return true;
+ }
- async function checkHasLLM(): Promise {
- // NOT IMPL
- return true
- }
+ function getBotList() {
+ httpClient
+ .getBots()
+ .then((resp) => {
+ const botList: BotCardVO[] = resp.bots.map((bot: Bot) => {
+ return new BotCardVO({
+ adapter: bot.adapter,
+ description: bot.description,
+ id: bot.uuid || "",
+ name: bot.name,
+ updateTime: bot.updated_at || "",
+ pipelineName: bot.use_pipeline_name || ""
+ });
+ });
+ if (botList.length === 0) {
+ setPageShowRule(BotConfigPageShowRule.NO_BOT);
+ } else {
+ setPageShowRule(BotConfigPageShowRule.HAVE_BOT);
+ }
+ setBotList(botList);
+ })
+ .catch((err) => {
+ console.error("get bot list error", err);
+ // TODO HACK: need refactor to hook mode Notification, but it's not working under render
+ notification.error({
+ message: "获取机器人列表失败",
+ description: err.message,
+ placement: "bottomRight"
+ });
+ })
+ .finally(() => {
+ setIsLoading(false);
+ });
+ }
- function getBotList() {
- httpClient.getBots().then((resp) => {
- const botList: BotCardVO[] = resp.bots.map((bot: Bot) => {
- return new BotCardVO({
- adapter: bot.adapter,
- description: bot.description,
- id: bot.uuid || "",
- name: bot.name,
- updateTime: bot.updated_at || "",
- pipelineName: bot.use_pipeline_name || "",
- })
- })
- if (botList.length === 0) {
- setPageShowRule(BotConfigPageShowRule.NO_BOT)
- } else {
- setPageShowRule(BotConfigPageShowRule.HAVE_BOT)
- }
- setBotList(botList)
- }).catch((err) => {
- console.error("get bot list error", err)
- // TODO HACK: need refactor to hook mode Notification, but it's not working under render
- notification.error({
- message: "获取机器人列表失败",
- description: err.message,
- placement: "bottomRight",
- })
- }).finally(() => {
- setIsLoading(false)
- })
- }
+ function handleCreateBotClick() {
+ setIsEditForm(false);
+ setNowSelectedCard(undefined);
+ setModalOpen(true);
+ }
- function handleCreateBotClick() {
- setIsEditForm(false)
- setNowSelectedCard(undefined)
- setModalOpen(true);
- }
+ function setNowSelectedCard(cardVO: BotCardVO | undefined) {
+ setNowSelectedBotCard(cardVO);
+ }
- function setNowSelectedCard(cardVO: BotCardVO | undefined) {
- setNowSelectedBotCard(cardVO)
- }
+ function selectBot(cardVO: BotCardVO) {
+ setIsEditForm(true);
+ setNowSelectedCard(cardVO);
+ console.log("set now vo", cardVO);
+ setModalOpen(true);
+ }
- function selectBot(cardVO: BotCardVO) {
- setIsEditForm(true)
- setNowSelectedCard(cardVO)
- console.log("set now vo", cardVO)
- setModalOpen(true)
- }
+ return (
+
+
+ setModalOpen(false)}
+ onCancel={() => setModalOpen(false)}
+ width={700}
+ footer={null}
+ destroyOnClose={true}
+ >
+ {
+ getBotList();
+ setModalOpen(false);
+ }}
+ onFormCancel={() => setModalOpen(false)}
+ />
+
+ {pageShowRule === BotConfigPageShowRule.NO_LLM && (
+ {
+ router.push("/home/models");
+ }}
+ />
+ )}
- return (
-
-
- {/* 删除 spin,使用 spin 会导致盒子塌陷。 */}
-
setModalOpen(false)}
- onCancel={() => setModalOpen(false)}
- width={700}
- footer={null}
- destroyOnClose={true}
- >
- {
- getBotList()
- setModalOpen(false)
- }}
- onFormCancel={() => setModalOpen(false)}
- />
-
- {pageShowRule === BotConfigPageShowRule.NO_LLM &&
-
{
- router.push("/home/models");
- }}
- />
- }
-
- {pageShowRule === BotConfigPageShowRule.NO_BOT &&
-
- }
-
- {pageShowRule === BotConfigPageShowRule.HAVE_BOT &&
-
- {botList.map(cardVO => {
- return (
-
{ selectBot(cardVO) }}
- >
-
-
)
- })}
-
-
- }
+ {pageShowRule === BotConfigPageShowRule.NO_BOT && (
+
+ )}
+
+ {/* 注意:其余的返回内容需要保持在Spin组件外部 */}
+ {pageShowRule === BotConfigPageShowRule.HAVE_BOT && (
+
+ {botList.map((cardVO) => {
+ return (
+
{
+ selectBot(cardVO);
+ }}
+ >
+
+
+ );
+ })}
+
-
- )
+ )}
+
+ );
}
enum BotConfigPageShowRule {
- NO_LLM,
- NO_BOT,
- HAVE_BOT,
-}
\ No newline at end of file
+ NO_LLM,
+ NO_BOT,
+ HAVE_BOT
+}
diff --git a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx
index 39c2be86..b6e149ae 100644
--- a/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx
+++ b/web/src/app/home/plugins/plugin-installed/PluginInstalledComponent.tsx
@@ -1,228 +1,60 @@
-"use client"
+"use client";
+import { useState, useEffect } from "react";
import CreateCardComponent from "@/app/infra/basic-component/create-card-component/CreateCardComponent";
import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO";
-import { useEffect, useState } from "react";
import PluginCardComponent from "@/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent";
import styles from "@/app/home/plugins/plugins.module.css";
import { Modal, Input } from "antd";
import { GithubOutlined } from "@ant-design/icons";
+import { httpClient } from "@/app/infra/http/HttpClient";
export default function PluginInstalledComponent() {
- const [pluginList, setPluginList] = useState([])
- const [modalOpen, setModalOpen] = useState(false)
- const [githubURL, setGithubURL] = useState("")
-
+ const [pluginList, setPluginList] = useState([]);
+ const [modalOpen, setModalOpen] = useState(false);
+ const [githubURL, setGithubURL] = useState("");
useEffect(() => {
initData();
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [])
+ }, []);
function initData() {
- getPluginList().then((value) => {
- setPluginList(value)
- })
+ getPluginList();
}
- async function getPluginList() {
- return [
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
- new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }), new PluginCardVO({
- description: "一般的描述",
- handlerCount: 0,
- name: "插件AAA",
- author: "/hana",
- version: "0.1",
- isInitialized: false
- }),
-
-
- ]
+ function getPluginList() {
+ httpClient.getPlugins().then((value) => {
+ setPluginList(
+ value.plugins.map((plugin) => {
+ return new PluginCardVO({
+ author: plugin.author,
+ description: plugin.description.zh_CN,
+ handlerCount: 0,
+ name: plugin.name,
+ version: plugin.version,
+ isInitialized: plugin.status === "initialized"
+ });
+ })
+ );
+ });
}
function handleModalConfirm() {
- installPlugin(githubURL)
- setModalOpen(false)
+ installPlugin(githubURL);
+ setModalOpen(false);
}
function installPlugin(url: string) {
- // TODO 接安装Plugin的接口
- console.log("installPlugin: ", url)
+ httpClient
+ .installPluginFromGithub(url)
+ .then(() => {
+ // 安装后重新拉取
+ getPluginList();
+ })
+ .catch((err) => {
+ console.log("error when install plugin:", err);
+ });
}
return (
@@ -231,25 +63,19 @@ export default function PluginInstalledComponent() {
- 从 GitHub 安装插件
}
- centered
open={modalOpen}
- onOk={() => handleModalConfirm()}
+ onOk={handleModalConfirm}
onCancel={() => setModalOpen(false)}
- width={500}
destroyOnClose={true}
>
-
- 目前仅支持从 GitHub 安装
-
+
目前仅支持从 GitHub 安装
- {
- pluginList.map((vo, index) => {
- return
+ {pluginList.map((vo, index) => {
+ return (
+
- })
- }
+ );
+ })}
{
- setModalOpen(true)
+ setModalOpen(true);
}}
/>
- )
+ );
}
diff --git a/web/src/app/infra/http/HttpClient.ts b/web/src/app/infra/http/HttpClient.ts
index a8bb53cc..58462059 100644
--- a/web/src/app/infra/http/HttpClient.ts
+++ b/web/src/app/infra/http/HttpClient.ts
@@ -26,7 +26,8 @@ import {
ApiRespSystemInfo,
ApiRespAsyncTasks,
ApiRespAsyncTask,
- ApiRespUserToken, MarketPluginResponse
+ ApiRespUserToken,
+ MarketPluginResponse
} from "../api/api-types";
import { notification } from "antd";
@@ -50,22 +51,19 @@ export interface RequestConfig extends AxiosRequestConfig {
class HttpClient {
private instance: AxiosInstance;
- private disableToken: boolean = false
+ private disableToken: boolean = false;
// 暂不需要SSR
// private ssrInstance: AxiosInstance | null = null
- constructor(
- baseURL?: string,
- disableToken?: boolean
- ) {
+ constructor(baseURL?: string, disableToken?: boolean) {
this.instance = axios.create({
baseURL: baseURL || this.getBaseUrl(),
timeout: 15000,
headers: {
- "Content-Type": "application/json",
+ "Content-Type": "application/json"
}
});
- this.disableToken = disableToken || false
+ this.disableToken = disableToken || false;
this.initInterceptors();
}
@@ -129,9 +127,9 @@ class HttpClient {
const errMessage = data?.message || error.message;
switch (status) {
- case 401:
- window.location.href = "/login";
- break;
+ // case 401:
+ // window.location.href = "/login";
+ // break;
case 403:
console.error("Permission denied:", errMessage);
break;
@@ -358,7 +356,7 @@ class HttpClient {
public getMarketPlugins(
page: number,
page_size: number,
- query: string,
+ query: string
): Promise
{
return this.post(`/api/v1/market/plugins`, {
page,
@@ -366,7 +364,7 @@ class HttpClient {
query,
sort_by: "stars",
sort_order: "DESC"
- })
+ });
}
public installPluginFromGithub(
source: string
@@ -415,4 +413,4 @@ class HttpClient {
export const httpClient = new HttpClient("https://version-4.langbot.dev");
// 临时写法,未来两种Client都继承自HttpClient父类,不允许共享方法
-export const spaceClient = new HttpClient("https://space.langbot.app")
+export const spaceClient = new HttpClient("https://space.langbot.app");
From db547fb3788bf1f4ae703196e3324f97c2a48e89 Mon Sep 17 00:00:00 2001
From: chris <1637083533@qq.com>
Date: Tue, 29 Apr 2025 15:36:03 +0800
Subject: [PATCH 6/8] =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=86=B2=E7=AA=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../plugin-card/PluginCardComponent.tsx | 114 +++++++++---------
1 file changed, 56 insertions(+), 58 deletions(-)
diff --git a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx
index 9efbbe9e..53b3ecf9 100644
--- a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx
+++ b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx
@@ -1,67 +1,65 @@
-import styles from "./pluginCard.module.css"
+import styles from "./pluginCard.module.css";
import { PluginCardVO } from "@/app/home/plugins/plugin-installed/PluginCardVO";
-import { GithubOutlined, LinkOutlined, ToolOutlined } from '@ant-design/icons';
-import { Switch, Tag } from 'antd'
+import { GithubOutlined, LinkOutlined, ToolOutlined } from "@ant-design/icons";
+import { Switch, Tag } from "antd";
import { useState } from "react";
import { httpClient } from "@/app/infra/http/HttpClient";
export default function PluginCardComponent({
- cardVO
+ cardVO
}: {
- cardVO: PluginCardVO
+ cardVO: PluginCardVO;
}) {
- const [initialized, setInitialized] = useState(cardVO.isInitialized);
- const [switchEnable, setSwitchEnable] = useState(true);
+ const [initialized, setInitialized] = useState(cardVO.isInitialized);
+ const [switchEnable, setSwitchEnable] = useState(true);
- function handleEnable() {
- setSwitchEnable(false);
- httpClient
- .togglePlugin(cardVO.author, cardVO.name, !initialized)
- .then(() => {
- setInitialized(!initialized);
- })
- .catch((err) => {
- console.log("error: ", err);
- })
- .finally(() => {
- setSwitchEnable(true);
- });
- }
-
- return (
-
- {/* header */}
-
- {/* left author */}
-
{cardVO.author}
- {/* right icon & version */}
-
-
- v{cardVO.version}
-
-
- {/* content */}
-
-
{cardVO.name}
-
{cardVO.description}
-
- {/* footer */}
-
+ function handleEnable() {
+ setSwitchEnable(false);
+ httpClient
+ .togglePlugin(cardVO.author, cardVO.name, !initialized)
+ .then(() => {
+ setInitialized(!initialized);
+ })
+ .catch((err) => {
+ console.log("error: ", err);
+ })
+ .finally(() => {
+ setSwitchEnable(true);
+ });
+ }
+ return (
+
+ {/* header */}
+
+ {/* left author */}
+
{cardVO.author}
+ {/* right icon & version */}
+
+
+ v{cardVO.version}
- );
-}
+
+ {/* content */}
+
+
{cardVO.name}
+
{cardVO.description}
+
+ {/* footer */}
+
+
+ );
+}
\ No newline at end of file
From 5c162009eee90d2594cbc879be7bb7d970ebe675 Mon Sep 17 00:00:00 2001
From: chris <1637083533@qq.com>
Date: Tue, 29 Apr 2025 15:41:17 +0800
Subject: [PATCH 7/8] =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=86=B2=E7=AA=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
web/src/app/infra/http/HttpClient.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/web/src/app/infra/http/HttpClient.ts b/web/src/app/infra/http/HttpClient.ts
index 58462059..7183ba59 100644
--- a/web/src/app/infra/http/HttpClient.ts
+++ b/web/src/app/infra/http/HttpClient.ts
@@ -127,9 +127,9 @@ class HttpClient {
const errMessage = data?.message || error.message;
switch (status) {
- // case 401:
- // window.location.href = "/login";
- // break;
+ case 401:
+ window.location.href = "/login";
+ break;
case 403:
console.error("Permission denied:", errMessage);
break;
From f1beb108930f3f93aaf64bd0e680efd9e816a209 Mon Sep 17 00:00:00 2001
From: chris <1637083533@qq.com>
Date: Tue, 29 Apr 2025 16:25:58 +0800
Subject: [PATCH 8/8] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8F=92=E4=BB=B6?=
=?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8D=A1=E7=89=87=E6=A0=B7=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../plugin-card/PluginCardComponent.tsx | 27 +++---
.../plugin-card/pluginCard.module.css | 21 +++++
.../PluginMarketCardComponent.tsx | 91 +++++++++----------
.../pluginMarketCard.module.css | 1 +
4 files changed, 79 insertions(+), 61 deletions(-)
diff --git a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx
index 53b3ecf9..8999534f 100644
--- a/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx
+++ b/web/src/app/home/plugins/plugin-installed/plugin-card/PluginCardComponent.tsx
@@ -46,20 +46,23 @@ export default function PluginCardComponent({
{/* footer */}
-
);
-}
\ No newline at end of file
+}
diff --git a/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css b/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css
index 3ec44fd4..1d09d462 100644
--- a/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css
+++ b/web/src/app/home/plugins/plugin-installed/plugin-card/pluginCard.module.css
@@ -41,8 +41,29 @@
.cardFooter {
width: 90%;
height: 30px;
+ position: relative;
}
+.footerContainer {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.linkAndToolContainer {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 10px;
+}
+
+.switchContainer {
+ display: flex;
+ justify-content: flex-end;
+}
.fontGray {
color: #6C6C6C;
diff --git a/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx b/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx
index 5d96c57f..dcc1dfd0 100644
--- a/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx
+++ b/web/src/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardComponent.tsx
@@ -1,56 +1,49 @@
-import styles from "./pluginMarketCard.module.css"
-import {GithubOutlined, StarOutlined} from '@ant-design/icons';
-import {PluginMarketCardVO} from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO";
-import {Button} from "antd";
+import styles from "./pluginMarketCard.module.css";
+import { GithubOutlined, StarOutlined } from "@ant-design/icons";
+import { PluginMarketCardVO } from "@/app/home/plugins/plugin-market/plugin-market-card/PluginMarketCardVO";
+import { Button } from "antd";
export default function PluginMarketCardComponent({
- cardVO
+ cardVO
}: {
- cardVO: PluginMarketCardVO
+ cardVO: PluginMarketCardVO;
}) {
+ function handleInstallClick(pluginId: string) {
+ console.log("Install plugin: ", pluginId);
+ }
-
- function handleInstallClick (pluginId: string) {
- console.log("Install plugin: ", pluginId)
- }
-
- return (
-
- {/* header */}
-
- {/* left author */}
-
{cardVO.author}
- {/* right icon */}
-
-
- {/* content */}
-
-
{cardVO.name}
-
{cardVO.description}
-
- {/* footer */}
-
-
-
-
- {cardVO.starCount}
-
-
-
-
+ return (
+
+ {/* header */}
+
+ {/* left author */}
+
{cardVO.author}
+ {/* right icon */}
+
+
+ {/* content */}
+
+
{cardVO.name}
+
{cardVO.description}
+
+ {/* footer */}
+
+
+
+
+ {cardVO.starCount}
+
- );
+
+
+
+ );
}
diff --git a/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css b/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css
index 33297b09..97a5e224 100644
--- a/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css
+++ b/web/src/app/home/plugins/plugin-market/plugin-market-card/pluginMarketCard.module.css
@@ -71,5 +71,6 @@
color: #6062E7;
align-self: center;
justify-content: space-between;
+ align-items: center;
}
}
\ No newline at end of file