mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-09 15:26:03 +00:00
Compare commits
2 Commits
v4.8.4
...
feat/napca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46a993b9a3 | ||
|
|
1eda076b93 |
@@ -18,6 +18,7 @@ import langbot_plugin.api.entities.builtin.provider.session as provider_session
|
|||||||
import langbot_plugin.api.entities.builtin.platform.events as platform_events
|
import langbot_plugin.api.entities.builtin.platform.events as platform_events
|
||||||
import langbot_plugin.api.entities.builtin.platform.message as platform_message
|
import langbot_plugin.api.entities.builtin.platform.message as platform_message
|
||||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||||
|
import langbot_plugin.api.entities.events as events
|
||||||
|
|
||||||
|
|
||||||
class RuntimeBot:
|
class RuntimeBot:
|
||||||
@@ -141,6 +142,56 @@ class RuntimeBot:
|
|||||||
self.adapter.register_listener(platform_events.FriendMessage, on_friend_message)
|
self.adapter.register_listener(platform_events.FriendMessage, on_friend_message)
|
||||||
self.adapter.register_listener(platform_events.GroupMessage, on_group_message)
|
self.adapter.register_listener(platform_events.GroupMessage, on_group_message)
|
||||||
|
|
||||||
|
async def on_notice(
|
||||||
|
event: platform_events.NoticeEvent,
|
||||||
|
adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
|
||||||
|
):
|
||||||
|
await self.logger.info(f'Notice event: {event.notice_type} {event.sub_type}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
event_obj = events.NoticeReceived(
|
||||||
|
notice_type=event.notice_type,
|
||||||
|
sub_type=event.sub_type,
|
||||||
|
group_id=event.group_id,
|
||||||
|
user_id=event.user_id,
|
||||||
|
operator_id=event.operator_id,
|
||||||
|
target_id=event.target_id,
|
||||||
|
message_id=event.message_id,
|
||||||
|
duration=event.duration,
|
||||||
|
file=event.file,
|
||||||
|
honor_type=event.honor_type,
|
||||||
|
)
|
||||||
|
|
||||||
|
if hasattr(self.ap, 'plugin_connector') and self.ap.plugin_connector:
|
||||||
|
await self.ap.plugin_connector.emit_event(event_obj)
|
||||||
|
except Exception:
|
||||||
|
await self.logger.error(f'Error emitting notice event: {traceback.format_exc()}')
|
||||||
|
|
||||||
|
self.adapter.register_listener(platform_events.NoticeEvent, on_notice)
|
||||||
|
|
||||||
|
async def on_request(
|
||||||
|
event: platform_events.RequestEvent,
|
||||||
|
adapter: abstract_platform_adapter.AbstractMessagePlatformAdapter,
|
||||||
|
):
|
||||||
|
await self.logger.info(f'Request event: {event.request_type} {event.sub_type}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
event_obj = events.RequestReceived(
|
||||||
|
request_type=event.request_type,
|
||||||
|
sub_type=event.sub_type,
|
||||||
|
user_id=event.user_id,
|
||||||
|
group_id=event.group_id,
|
||||||
|
comment=event.comment,
|
||||||
|
flag=event.flag,
|
||||||
|
)
|
||||||
|
|
||||||
|
if hasattr(self.ap, 'plugin_connector') and self.ap.plugin_connector:
|
||||||
|
await self.ap.plugin_connector.emit_event(event_obj)
|
||||||
|
except Exception:
|
||||||
|
await self.logger.error(f'Error emitting request event: {traceback.format_exc()}')
|
||||||
|
|
||||||
|
self.adapter.register_listener(platform_events.RequestEvent, on_request)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
async def exception_wrapper():
|
async def exception_wrapper():
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -306,9 +306,8 @@ class AiocqhttpEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def target2yiri(event: aiocqhttp.Event, bot=None):
|
async def target2yiri(event: aiocqhttp.Event, bot=None):
|
||||||
yiri_chain = await AiocqhttpMessageConverter.target2yiri(event.message, event.message_id, bot)
|
|
||||||
|
|
||||||
if event.message_type == 'group':
|
if event.message_type == 'group':
|
||||||
|
yiri_chain = await AiocqhttpMessageConverter.target2yiri(event.message, event.message_id, bot)
|
||||||
permission = 'MEMBER'
|
permission = 'MEMBER'
|
||||||
|
|
||||||
if 'role' in event.sender:
|
if 'role' in event.sender:
|
||||||
@@ -334,6 +333,7 @@ class AiocqhttpEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
|||||||
)
|
)
|
||||||
return converted_event
|
return converted_event
|
||||||
elif event.message_type == 'private':
|
elif event.message_type == 'private':
|
||||||
|
yiri_chain = await AiocqhttpMessageConverter.target2yiri(event.message, event.message_id, bot)
|
||||||
return platform_events.FriendMessage(
|
return platform_events.FriendMessage(
|
||||||
sender=platform_entities.Friend(
|
sender=platform_entities.Friend(
|
||||||
id=event.sender['user_id'],
|
id=event.sender['user_id'],
|
||||||
@@ -344,6 +344,57 @@ class AiocqhttpEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
|||||||
time=event.time,
|
time=event.time,
|
||||||
source_platform_object=event,
|
source_platform_object=event,
|
||||||
)
|
)
|
||||||
|
elif event.post_type == 'notice':
|
||||||
|
yiri_chain = platform_message.MessageChain(
|
||||||
|
[
|
||||||
|
platform_message.Source(id=-1, time=datetime.datetime.now()),
|
||||||
|
platform_message.Notice(
|
||||||
|
notice_type=event.get('notice_type', ''),
|
||||||
|
sub_type=event.get('sub_type', ''),
|
||||||
|
user_id=event.get('user_id', None),
|
||||||
|
target_id=event.get('target_id', None),
|
||||||
|
group_id=event.get('group_id', None),
|
||||||
|
operator_id=event.get('operator_id', None),
|
||||||
|
message_id=event.get('message_id', None),
|
||||||
|
duration=event.get('duration', None),
|
||||||
|
file=event.get('file', None),
|
||||||
|
honor_type=event.get('honor_type', None),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return platform_events.NoticeEvent(
|
||||||
|
notice_type=event.get('notice_type', ''),
|
||||||
|
sub_type=event.get('sub_type', ''),
|
||||||
|
user_id=event.get('user_id', None),
|
||||||
|
target_id=event.get('target_id', None),
|
||||||
|
group_id=event.get('group_id', None),
|
||||||
|
time=event.time,
|
||||||
|
source_platform_object=event,
|
||||||
|
)
|
||||||
|
elif event.post_type == 'request':
|
||||||
|
yiri_chain = platform_message.MessageChain(
|
||||||
|
[
|
||||||
|
platform_message.Source(id=-1, time=datetime.datetime.now()),
|
||||||
|
platform_message.Request(
|
||||||
|
request_type=event.get('request_type', ''),
|
||||||
|
sub_type=event.get('sub_type', ''),
|
||||||
|
user_id=event.get('user_id', None),
|
||||||
|
group_id=event.get('group_id', None),
|
||||||
|
comment=event.get('comment', ''),
|
||||||
|
flag=event.get('flag', ''),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
return platform_events.RequestEvent(
|
||||||
|
request_type=event.get('request_type', ''),
|
||||||
|
sub_type=event.get('sub_type', ''),
|
||||||
|
user_id=event.get('user_id', None),
|
||||||
|
group_id=event.get('group_id', None),
|
||||||
|
comment=event.get('comment', ''),
|
||||||
|
flag=event.get('flag', ''),
|
||||||
|
time=event.time,
|
||||||
|
source_platform_object=event,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AiocqhttpAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
class AiocqhttpAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||||
@@ -413,12 +464,31 @@ class AiocqhttpAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter)
|
|||||||
await self.logger.error(f'Error in on_message: {traceback.format_exc()}')
|
await self.logger.error(f'Error in on_message: {traceback.format_exc()}')
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
async def on_notice(event: aiocqhttp.Event):
|
||||||
|
self.bot_account_id = event.self_id
|
||||||
|
try:
|
||||||
|
return await callback(await self.event_converter.target2yiri(event, self.bot), self)
|
||||||
|
except Exception:
|
||||||
|
await self.logger.error(f'Error in on_notice: {traceback.format_exc()}')
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
async def on_request(event: aiocqhttp.Event):
|
||||||
|
self.bot_account_id = event.self_id
|
||||||
|
try:
|
||||||
|
return await callback(await self.event_converter.target2yiri(event, self.bot), self)
|
||||||
|
except Exception:
|
||||||
|
await self.logger.error(f'Error in on_request: {traceback.format_exc()}')
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
if event_type == platform_events.GroupMessage:
|
if event_type == platform_events.GroupMessage:
|
||||||
self.bot.on_message('group')(on_message)
|
self.bot.on_message('group')(on_message)
|
||||||
# self.bot.on_notice()(on_message)
|
# self.bot.on_notice()(on_message)
|
||||||
elif event_type == platform_events.FriendMessage:
|
elif event_type == platform_events.FriendMessage:
|
||||||
self.bot.on_message('private')(on_message)
|
self.bot.on_message('private')(on_message)
|
||||||
# self.bot.on_notice()(on_message)
|
elif event_type == platform_events.NoticeEvent:
|
||||||
|
self.bot.on_notice()(on_notice)
|
||||||
|
elif event_type == platform_events.RequestEvent:
|
||||||
|
self.bot.on_request()(on_request)
|
||||||
# print(event_type)
|
# print(event_type)
|
||||||
|
|
||||||
async def on_websocket_connection(event: aiocqhttp.Event):
|
async def on_websocket_connection(event: aiocqhttp.Event):
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef, Suspense } from 'react';
|
import {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
useRef,
|
||||||
|
Suspense,
|
||||||
|
useMemo,
|
||||||
|
} from 'react';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@@ -23,6 +30,8 @@ import { LoadingSpinner } from '@/components/ui/loading-spinner';
|
|||||||
import { TagsFilter } from './TagsFilter';
|
import { TagsFilter } from './TagsFilter';
|
||||||
import { PluginTag } from '@/app/infra/http/CloudServiceClient';
|
import { PluginTag } from '@/app/infra/http/CloudServiceClient';
|
||||||
|
|
||||||
|
import { RecommendationLists, RecommendationList } from './RecommendationLists';
|
||||||
|
|
||||||
interface SortOption {
|
interface SortOption {
|
||||||
value: string;
|
value: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -50,6 +59,9 @@ function MarketPageContent({
|
|||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [sortOption, setSortOption] = useState('install_count_desc');
|
const [sortOption, setSortOption] = useState('install_count_desc');
|
||||||
|
const [recommendationLists, setRecommendationLists] = useState<
|
||||||
|
RecommendationList[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
const pageSize = 16; // 每页16个,4行x4列
|
const pageSize = 16; // 每页16个,4行x4列
|
||||||
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
@@ -203,6 +215,20 @@ function MarketPageContent({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fetch recommendation lists
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchRecommendationLists() {
|
||||||
|
try {
|
||||||
|
const response =
|
||||||
|
await getCloudServiceClientSync().getRecommendationLists();
|
||||||
|
setRecommendationLists(response.lists || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch recommendation lists:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchRecommendationLists();
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 搜索功能
|
// 搜索功能
|
||||||
const handleSearch = useCallback(
|
const handleSearch = useCallback(
|
||||||
(query: string) => {
|
(query: string) => {
|
||||||
@@ -306,6 +332,39 @@ function MarketPageContent({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 计算所有推荐插件的 ID 集合
|
||||||
|
const recommendedPluginIds = useMemo(() => {
|
||||||
|
const ids = new Set<string>();
|
||||||
|
recommendationLists.forEach((list) => {
|
||||||
|
list.plugins.forEach((plugin) => {
|
||||||
|
ids.add(`${plugin.author} / ${plugin.name}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return ids;
|
||||||
|
}, [recommendationLists]);
|
||||||
|
|
||||||
|
// 过滤掉已在推荐列表中展示的插件
|
||||||
|
// 仅在显示推荐列表的条件下(无搜索、无筛选、第一页或后续页的累积数据中)进行过滤
|
||||||
|
// 注意:如果用户翻页,我们希望一直保持去重,否则推荐过的插件会在第二页出现
|
||||||
|
// 但是推荐列表只在第一页且无筛选时显示。
|
||||||
|
// 如果用户进行了筛选/搜索,推荐列表不显示,此时不需要去重。
|
||||||
|
const visiblePlugins = useMemo(() => {
|
||||||
|
const showRecommendations =
|
||||||
|
!searchQuery && componentFilter === 'all' && selectedTags.length === 0;
|
||||||
|
|
||||||
|
if (!showRecommendations) {
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
return plugins.filter((p) => !recommendedPluginIds.has(p.pluginId));
|
||||||
|
}, [
|
||||||
|
plugins,
|
||||||
|
recommendedPluginIds,
|
||||||
|
searchQuery,
|
||||||
|
componentFilter,
|
||||||
|
selectedTags,
|
||||||
|
]);
|
||||||
|
|
||||||
// 加载更多
|
// 加载更多
|
||||||
const loadMore = useCallback(() => {
|
const loadMore = useCallback(() => {
|
||||||
if (!isLoadingMore && hasMore) {
|
if (!isLoadingMore && hasMore) {
|
||||||
@@ -494,6 +553,20 @@ function MarketPageContent({
|
|||||||
ref={scrollContainerRef}
|
ref={scrollContainerRef}
|
||||||
className="flex-1 overflow-y-auto px-3 sm:px-4"
|
className="flex-1 overflow-y-auto px-3 sm:px-4"
|
||||||
>
|
>
|
||||||
|
{/* Recommendation Lists */}
|
||||||
|
{!searchQuery &&
|
||||||
|
componentFilter === 'all' &&
|
||||||
|
selectedTags.length === 0 &&
|
||||||
|
currentPage === 1 && (
|
||||||
|
<div className="pt-4">
|
||||||
|
<RecommendationLists
|
||||||
|
lists={recommendationLists}
|
||||||
|
tagNames={tagNames}
|
||||||
|
onInstall={handleInstallPlugin}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
<LoadingSpinner text={t('market.loading')} />
|
<LoadingSpinner text={t('market.loading')} />
|
||||||
@@ -507,7 +580,7 @@ function MarketPageContent({
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-6 pb-6 pt-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-6 pb-6 pt-4">
|
||||||
{plugins.map((plugin) => (
|
{visiblePlugins.map((plugin) => (
|
||||||
<PluginMarketCardComponent
|
<PluginMarketCardComponent
|
||||||
key={plugin.pluginId}
|
key={plugin.pluginId}
|
||||||
cardVO={plugin}
|
cardVO={plugin}
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { ChevronLeft, ChevronRight, Star } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import PluginMarketCardComponent from './plugin-market-card/PluginMarketCardComponent';
|
||||||
|
import { PluginMarketCardVO } from './plugin-market-card/PluginMarketCardVO';
|
||||||
|
import { PluginV4 } from '@/app/infra/entities/plugin';
|
||||||
|
import { I18nObject } from '@/app/infra/entities/common';
|
||||||
|
import { extractI18nObject } from '@/i18n/I18nProvider';
|
||||||
|
import { getCloudServiceClientSync } from '@/app/infra/http';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export interface RecommendationList {
|
||||||
|
uuid: string;
|
||||||
|
label: I18nObject;
|
||||||
|
sort_order: number;
|
||||||
|
plugins: PluginV4[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const PAGE_SIZE = 4; // plugins per page in a recommendation row
|
||||||
|
|
||||||
|
function pluginToVO(
|
||||||
|
plugin: PluginV4,
|
||||||
|
t: (key: string) => string,
|
||||||
|
): PluginMarketCardVO {
|
||||||
|
return new PluginMarketCardVO({
|
||||||
|
pluginId: plugin.author + ' / ' + plugin.name,
|
||||||
|
author: plugin.author,
|
||||||
|
pluginName: plugin.name,
|
||||||
|
label: extractI18nObject(plugin.label),
|
||||||
|
description:
|
||||||
|
extractI18nObject(plugin.description) || t('market.noDescription'),
|
||||||
|
installCount: plugin.install_count,
|
||||||
|
iconURL: getCloudServiceClientSync().getPluginIconURL(
|
||||||
|
plugin.author,
|
||||||
|
plugin.name,
|
||||||
|
),
|
||||||
|
githubURL: plugin.repository,
|
||||||
|
version: plugin.latest_version,
|
||||||
|
components: plugin.components,
|
||||||
|
tags: plugin.tags || [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function RecommendationListRow({
|
||||||
|
list,
|
||||||
|
tagNames,
|
||||||
|
onInstall,
|
||||||
|
}: {
|
||||||
|
list: RecommendationList;
|
||||||
|
tagNames: Record<string, string>;
|
||||||
|
onInstall: (author: string, pluginName: string) => void;
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [page, setPage] = useState(0);
|
||||||
|
|
||||||
|
const plugins = list.plugins || [];
|
||||||
|
const totalPages = Math.ceil(plugins.length / PAGE_SIZE);
|
||||||
|
const start = page * PAGE_SIZE;
|
||||||
|
const visiblePlugins = plugins.slice(start, start + PAGE_SIZE);
|
||||||
|
|
||||||
|
if (plugins.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-6">
|
||||||
|
<div className="flex items-center justify-between mb-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Star className="w-4 h-4 text-yellow-500" />
|
||||||
|
<h3 className="font-semibold text-base">
|
||||||
|
{extractI18nObject(list.label)}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setPage((p) => Math.max(0, p - 1))}
|
||||||
|
disabled={page === 0}
|
||||||
|
className="h-7 w-7 p-0"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
<span className="text-xs text-muted-foreground px-1">
|
||||||
|
{page + 1} / {totalPages}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setPage((p) => Math.min(totalPages - 1, p + 1))}
|
||||||
|
disabled={page >= totalPages - 1}
|
||||||
|
className="h-7 w-7 p-0"
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-6">
|
||||||
|
{visiblePlugins.map((plugin) => (
|
||||||
|
<PluginMarketCardComponent
|
||||||
|
key={plugin.author + ' / ' + plugin.name}
|
||||||
|
cardVO={pluginToVO(plugin, t)}
|
||||||
|
tagNames={tagNames}
|
||||||
|
onInstall={onInstall}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{totalPages > 1 && <div className="border-b border-border mt-6" />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RecommendationLists({
|
||||||
|
lists,
|
||||||
|
tagNames,
|
||||||
|
onInstall,
|
||||||
|
}: {
|
||||||
|
lists: RecommendationList[];
|
||||||
|
tagNames: Record<string, string>;
|
||||||
|
onInstall: (author: string, pluginName: string) => void;
|
||||||
|
}) {
|
||||||
|
if (!lists || lists.length === 0) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-6">
|
||||||
|
{lists.map((list) => (
|
||||||
|
<RecommendationListRow
|
||||||
|
key={list.uuid}
|
||||||
|
list={list}
|
||||||
|
tagNames={tagNames}
|
||||||
|
onInstall={onInstall}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<div className="border-b border-border mb-6" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ import {
|
|||||||
ApiRespMarketplacePluginDetail,
|
ApiRespMarketplacePluginDetail,
|
||||||
ApiRespMarketplacePlugins,
|
ApiRespMarketplacePlugins,
|
||||||
} from '@/app/infra/entities/api';
|
} from '@/app/infra/entities/api';
|
||||||
|
import { PluginV4 } from '@/app/infra/entities/plugin';
|
||||||
|
import { I18nObject } from '@/app/infra/entities/common';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 云服务客户端
|
* 云服务客户端
|
||||||
@@ -98,6 +100,19 @@ export class CloudServiceClient extends BaseHttpClient {
|
|||||||
public getAllTags(): Promise<{ tags: PluginTag[] }> {
|
public getAllTags(): Promise<{ tags: PluginTag[] }> {
|
||||||
return this.get<{ tags: PluginTag[] }>('/api/v1/marketplace/tags');
|
return this.get<{ tags: PluginTag[] }>('/api/v1/marketplace/tags');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getRecommendationLists(): Promise<{ lists: RecommendationList[] }> {
|
||||||
|
return this.get<{ lists: RecommendationList[] }>(
|
||||||
|
'/api/v1/marketplace/recommendation-lists',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RecommendationList {
|
||||||
|
uuid: string;
|
||||||
|
label: I18nObject;
|
||||||
|
sort_order: number;
|
||||||
|
plugins: PluginV4[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PluginTag {
|
export interface PluginTag {
|
||||||
|
|||||||
Reference in New Issue
Block a user