Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "maibot-dashboard",
|
||||
"private": true,
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"type": "module",
|
||||
"main": "./out/main/index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -29,7 +29,7 @@ export function Sidebar({
|
||||
return (
|
||||
<aside
|
||||
className={cn(
|
||||
'fixed inset-y-0 left-0 z-50 isolate flex flex-col border-r transition-all duration-300 lg:relative lg:z-0',
|
||||
'fixed inset-y-0 left-0 z-50 isolate flex flex-col border-r transition-all duration-300 lg:relative lg:z-0 lg:h-full',
|
||||
inheritsPageBackground ? 'bg-transparent' : 'bg-card',
|
||||
// 移动端始终显示完整宽度,桌面端根据 sidebarOpen 切换
|
||||
'w-64 lg:w-auto',
|
||||
@@ -46,9 +46,11 @@ export function Sidebar({
|
||||
|
||||
<ScrollArea className={cn(
|
||||
'relative z-10',
|
||||
"flex-1 overflow-x-hidden",
|
||||
"min-h-0 flex-1 overflow-x-hidden",
|
||||
!sidebarOpen && "lg:w-16"
|
||||
)}>
|
||||
)}
|
||||
viewportClassName="[&>div]:!block"
|
||||
>
|
||||
<nav
|
||||
aria-label={t('a11y.sidebarNav')}
|
||||
className={cn(
|
||||
|
||||
@@ -6,7 +6,9 @@ import { useTour } from './use-tour'
|
||||
// Joyride 主题配置
|
||||
const joyrideStyles = {
|
||||
options: {
|
||||
zIndex: 10000,
|
||||
// 提到 portal 容器(99999)之上,确保 overlay/spotlight/tooltip 都在最上层;
|
||||
// overlay 的 z-index 由 react-joyride 内部基于 options.zIndex 推算,必须大于 floater 才能让 tooltip 按钮可点击。
|
||||
zIndex: 100000,
|
||||
primaryColor: 'hsl(var(--color-primary))',
|
||||
textColor: 'hsl(var(--color-foreground))',
|
||||
backgroundColor: 'hsl(var(--color-background))',
|
||||
@@ -197,13 +199,6 @@ export function TourRenderer() {
|
||||
locale={locale}
|
||||
scrollOffset={80}
|
||||
scrollToFirstStep
|
||||
floaterProps={{
|
||||
styles: {
|
||||
floater: {
|
||||
zIndex: 99999,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-[min(calc(100vw-2rem),var(--dialog-width,32rem))] max-h-[calc(100vh-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 overflow-hidden border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
"fixed left-[50%] top-[50%] z-50 flex w-[min(calc(100vw-2rem),var(--dialog-width,32rem))] max-h-[calc(100vh-2rem)] translate-x-[-50%] translate-y-[-50%] flex-col gap-4 overflow-hidden border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
onPointerDownOutside={preventOutsideClose ? (e) => e.preventDefault() : undefined}
|
||||
@@ -94,13 +94,17 @@ const DialogContent = React.forwardRef<
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
const DialogBody = React.forwardRef<HTMLDivElement, DialogBodyProps>(
|
||||
({ className, children, allowHorizontalScroll = false, contentClassName, scrollbars, viewportClassName, ...props }, ref) => (
|
||||
({ className, children, allowHorizontalScroll = false, contentClassName, scrollbars, viewportClassName, type, ...props }, ref) => (
|
||||
// 关键:在 flex-col 的 DialogContent 中,DialogBody 既要在内容多时撑到 max-h 上限并滚动,
|
||||
// 又要在内容少时让 dialog 自然收缩。直接在 ScrollArea Root 上 flex-1 + min-h-0 即可:
|
||||
// Radix Viewport 内部 wrapper 默认 display:table 会撑开自然高度,所以需要强制 block。
|
||||
<ScrollArea
|
||||
ref={ref as never}
|
||||
className={cn("min-h-0 flex-1", className)}
|
||||
className={cn("min-h-0 flex-1 flex flex-col", className)}
|
||||
contentClassName={cn(allowHorizontalScroll && "min-w-full w-max", contentClassName)}
|
||||
scrollbars={scrollbars ?? (allowHorizontalScroll ? "both" : "vertical")}
|
||||
viewportClassName={cn("pr-4", viewportClassName)}
|
||||
viewportClassName={cn("min-h-0 flex-1 pr-4 [&>div]:!block", viewportClassName)}
|
||||
type={type ?? "always"}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -19,7 +19,10 @@ const ScrollArea = React.forwardRef<
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport ref={viewportRef} className={cn("h-full w-full rounded-[inherit]", viewportClassName)}>
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
ref={viewportRef}
|
||||
className={cn("h-full w-full rounded-[inherit]", viewportClassName)}
|
||||
>
|
||||
<div className={contentClassName}>{children}</div>
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
{scrollbars !== "horizontal" && <ScrollBar />}
|
||||
|
||||
@@ -158,7 +158,14 @@ export async function fetchProviderModels(
|
||||
endpoint,
|
||||
})
|
||||
const response = await fetchWithAuth(`/api/webui/models/list?${params}`)
|
||||
return parseResponse<ModelListItem[]>(response)
|
||||
// 后端返回 { success, models, provider, count },需要展开取出 models 数组
|
||||
const parsed = await parseResponse<{ models?: ModelListItem[] } | ModelListItem[]>(response)
|
||||
if (!parsed.success) {
|
||||
return parsed
|
||||
}
|
||||
const body = parsed.data
|
||||
const models = Array.isArray(body) ? body : Array.isArray(body?.models) ? body.models : []
|
||||
return { success: true, data: models }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 修改此处的版本号后,所有展示版本的地方都会自动更新
|
||||
*/
|
||||
|
||||
export const APP_VERSION = '1.0.1'
|
||||
export const APP_VERSION = '1.0.2'
|
||||
export const APP_NAME = 'MaiBot Dashboard'
|
||||
export const APP_FULL_NAME = `${APP_NAME} v${APP_VERSION}`
|
||||
|
||||
|
||||
@@ -85,11 +85,11 @@ const modelConfigRoute = createRoute({
|
||||
component: lazyRouteComponent(() => import('./routes/config/model'), 'ModelConfigPage'),
|
||||
})
|
||||
|
||||
// 配置路由 - 麦麦适配器配置
|
||||
// 配置路由 - 麦麦适配器配置(已停用,引导跳转到插件配置;旧实现保留在 ./routes/config/adapter)
|
||||
const adapterConfigRoute = createRoute({
|
||||
getParentRoute: () => protectedRoute,
|
||||
path: '/config/adapter',
|
||||
component: lazyRouteComponent(() => import('./routes/config/adapter'), 'AdapterConfigPage'),
|
||||
component: lazyRouteComponent(() => import('./routes/config/adapter-disabled'), 'AdapterConfigPage'),
|
||||
})
|
||||
|
||||
// 资源管理路由 - 表情包管理
|
||||
|
||||
60
dashboard/src/routes/config/adapter-disabled.tsx
Normal file
60
dashboard/src/routes/config/adapter-disabled.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { ArrowRight, Info } from 'lucide-react'
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
|
||||
/**
|
||||
* 麦麦适配器配置 —— 禁用页
|
||||
*
|
||||
* 原页面({@link import('./adapter').AdapterConfigPage})的能力已迁移至
|
||||
* 「插件配置」中的对应适配器插件。这里保留路由占位并引导用户跳转,
|
||||
* 避免误用旧的 TOML 直接编辑路径。
|
||||
*/
|
||||
export function AdapterConfigPage() {
|
||||
return (
|
||||
<ScrollArea className="h-full">
|
||||
<div className="mx-auto w-full max-w-3xl space-y-4 p-4 sm:space-y-6 sm:p-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold sm:text-3xl">麦麦适配器配置</h1>
|
||||
<p className="text-muted-foreground mt-1 text-sm sm:mt-2 sm:text-base">
|
||||
该界面已停用
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Alert>
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertTitle>该配置入口已迁移</AlertTitle>
|
||||
<AlertDescription>
|
||||
适配器现已作为插件管理。请前往「插件配置」找到对应适配器插件(如 Napcat 适配器)进行配置。
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>请前往插件配置</CardTitle>
|
||||
<CardDescription>
|
||||
在插件配置页面中,选择目标适配器插件即可修改其配置项。原适配器 TOML 直接编辑入口已停用,但相关代码与历史配置文件未被删除,可在需要时由开发者手动恢复。
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button asChild>
|
||||
<Link to="/plugin-config">
|
||||
打开插件配置
|
||||
<ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
@@ -138,7 +138,11 @@ export function ProviderForm({
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={(e) => { e.preventDefault(); handleSaveEdit(); }} autoComplete="off">
|
||||
<form
|
||||
onSubmit={(e) => { e.preventDefault(); handleSaveEdit(); }}
|
||||
autoComplete="off"
|
||||
className="contents"
|
||||
>
|
||||
<DialogBody>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2" data-tour="provider-template-select">
|
||||
|
||||
@@ -16,6 +16,7 @@ interface InstalledTabProps {
|
||||
checkPluginCompatibility: (plugin: PluginInfo) => boolean
|
||||
needsUpdate: (plugin: PluginInfo) => boolean
|
||||
getStatusBadge: (plugin: PluginInfo) => React.JSX.Element | null
|
||||
getIncompatibleReason: (plugin: PluginInfo) => string | null
|
||||
}
|
||||
|
||||
export function InstalledTab({
|
||||
@@ -33,6 +34,7 @@ export function InstalledTab({
|
||||
checkPluginCompatibility,
|
||||
needsUpdate,
|
||||
getStatusBadge,
|
||||
getIncompatibleReason,
|
||||
}: InstalledTabProps) {
|
||||
// 过滤已安装插件
|
||||
const filteredPlugins = plugins.filter(plugin => {
|
||||
@@ -80,6 +82,7 @@ export function InstalledTab({
|
||||
checkPluginCompatibility={checkPluginCompatibility}
|
||||
needsUpdate={needsUpdate}
|
||||
getStatusBadge={getStatusBadge}
|
||||
getIncompatibleReason={getIncompatibleReason}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ interface MarketplaceTabProps {
|
||||
checkPluginCompatibility: (plugin: PluginInfo) => boolean
|
||||
needsUpdate: (plugin: PluginInfo) => boolean
|
||||
getStatusBadge: (plugin: PluginInfo) => React.JSX.Element | null
|
||||
getIncompatibleReason: (plugin: PluginInfo) => string | null
|
||||
}
|
||||
|
||||
export function MarketplaceTab({
|
||||
@@ -33,6 +34,7 @@ export function MarketplaceTab({
|
||||
checkPluginCompatibility,
|
||||
needsUpdate,
|
||||
getStatusBadge,
|
||||
getIncompatibleReason,
|
||||
}: MarketplaceTabProps) {
|
||||
// 过滤插件
|
||||
const filteredPlugins = plugins.filter(plugin => {
|
||||
@@ -76,6 +78,7 @@ export function MarketplaceTab({
|
||||
checkPluginCompatibility={checkPluginCompatibility}
|
||||
needsUpdate={needsUpdate}
|
||||
getStatusBadge={getStatusBadge}
|
||||
getIncompatibleReason={getIncompatibleReason}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ interface PluginCardProps {
|
||||
checkPluginCompatibility: (plugin: PluginInfo) => boolean
|
||||
needsUpdate: (plugin: PluginInfo) => boolean
|
||||
getStatusBadge: (plugin: PluginInfo) => React.JSX.Element | null
|
||||
getIncompatibleReason: (plugin: PluginInfo) => string | null
|
||||
}
|
||||
|
||||
export function PluginCard({
|
||||
@@ -34,6 +35,7 @@ export function PluginCard({
|
||||
checkPluginCompatibility,
|
||||
needsUpdate,
|
||||
getStatusBadge,
|
||||
getIncompatibleReason,
|
||||
}: PluginCardProps) {
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -114,8 +116,14 @@ export function PluginCard({
|
||||
needsUpdate(plugin) ? (
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={!gitStatus?.installed}
|
||||
title={!gitStatus?.installed ? 'Git 未安装' : undefined}
|
||||
disabled={!gitStatus?.installed || (maimaiVersion !== null && !checkPluginCompatibility(plugin))}
|
||||
title={
|
||||
!gitStatus?.installed
|
||||
? 'Git 未安装'
|
||||
: (maimaiVersion !== null && !checkPluginCompatibility(plugin))
|
||||
? (getIncompatibleReason(plugin) ?? '插件与当前麦麦版本不兼容')
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onUpdate(plugin)}
|
||||
>
|
||||
<RefreshCw className="h-4 w-4 mr-1" />
|
||||
@@ -145,7 +153,7 @@ export function PluginCard({
|
||||
!gitStatus?.installed
|
||||
? 'Git 未安装'
|
||||
: (maimaiVersion !== null && !checkPluginCompatibility(plugin))
|
||||
? `不兼容当前版本 (需要 ${plugin.manifest?.host_application?.min_version || '未知'}${plugin.manifest?.host_application?.max_version ? ` - ${plugin.manifest.host_application.max_version}` : '+'},当前 ${maimaiVersion?.version})`
|
||||
? (getIncompatibleReason(plugin) ?? '插件与当前麦麦版本不兼容')
|
||||
: undefined
|
||||
}
|
||||
onClick={() => onInstall(plugin)}
|
||||
|
||||
@@ -268,8 +268,8 @@ function PluginsPageContent() {
|
||||
|
||||
// 获取插件状态徽章
|
||||
const getStatusBadge = (plugin: PluginInfo) => {
|
||||
// 优先显示兼容性状态
|
||||
if (!plugin.installed && maimaiVersion && !checkPluginCompatibility(plugin)) {
|
||||
// 优先显示兼容性状态(已安装但不兼容也需要提示,避免用户误以为可继续更新)
|
||||
if (maimaiVersion && !checkPluginCompatibility(plugin)) {
|
||||
return (
|
||||
<Badge variant="destructive" className="gap-1">
|
||||
<AlertCircle className="h-3 w-3" />
|
||||
@@ -317,9 +317,20 @@ function PluginsPageContent() {
|
||||
}
|
||||
|
||||
// 检查插件兼容性
|
||||
// 规则:
|
||||
// 1. manifest_version === 1 的插件在麦麦 >= 1.0.0 时一律视为不兼容(旧 manifest 已不再被宿主接受);
|
||||
// 2. 否则若声明了 host_application 范围,则按版本范围判定。
|
||||
const checkPluginCompatibility = (plugin: PluginInfo): boolean => {
|
||||
if (!maimaiVersion || !plugin.manifest?.host_application) return true
|
||||
|
||||
if (!maimaiVersion) return true
|
||||
|
||||
// manifest v1 在 1.0.0+ 麦麦上不再兼容
|
||||
const manifestVersion = plugin.manifest?.manifest_version ?? 1
|
||||
if (manifestVersion <= 1 && maimaiVersion.version_major >= 1) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!plugin.manifest?.host_application) return true
|
||||
|
||||
return isPluginCompatible(
|
||||
plugin.manifest.host_application.min_version,
|
||||
plugin.manifest.host_application.max_version,
|
||||
@@ -327,11 +338,35 @@ function PluginsPageContent() {
|
||||
)
|
||||
}
|
||||
|
||||
// 不兼容原因(用于 UI 提示)
|
||||
const getIncompatibleReason = (plugin: PluginInfo): string | null => {
|
||||
if (!maimaiVersion) return null
|
||||
const manifestVersion = plugin.manifest?.manifest_version ?? 1
|
||||
if (manifestVersion <= 1 && maimaiVersion.version_major >= 1) {
|
||||
return `该插件使用旧版 manifest (v${manifestVersion}),已不被麦麦 ${maimaiVersion.version} 支持`
|
||||
}
|
||||
if (plugin.manifest?.host_application && !isPluginCompatible(
|
||||
plugin.manifest.host_application.min_version,
|
||||
plugin.manifest.host_application.max_version,
|
||||
maimaiVersion
|
||||
)) {
|
||||
const min = plugin.manifest.host_application.min_version || '未知'
|
||||
const max = plugin.manifest.host_application.max_version
|
||||
const range = max ? `${min} - ${max}` : `${min}+`
|
||||
return `不兼容当前版本 (需要 ${range},当前 ${maimaiVersion.version})`
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 检查是否需要更新(市场版本比已安装版本新)
|
||||
const needsUpdate = (plugin: PluginInfo): boolean => {
|
||||
if (!plugin.installed || !plugin.installed_version || !plugin.manifest?.version) {
|
||||
return false
|
||||
}
|
||||
// 不兼容的插件不允许更新
|
||||
if (!checkPluginCompatibility(plugin)) {
|
||||
return false
|
||||
}
|
||||
|
||||
const installedVer = plugin.installed_version.trim()
|
||||
const marketVer = plugin.manifest.version.trim()
|
||||
@@ -368,7 +403,7 @@ function PluginsPageContent() {
|
||||
if (maimaiVersion && !checkPluginCompatibility(plugin)) {
|
||||
toast({
|
||||
title: '无法安装',
|
||||
description: '插件与当前麦麦版本不兼容',
|
||||
description: getIncompatibleReason(plugin) ?? '插件与当前麦麦版本不兼容',
|
||||
variant: 'destructive',
|
||||
})
|
||||
return
|
||||
@@ -526,6 +561,16 @@ function PluginsPageContent() {
|
||||
return
|
||||
}
|
||||
|
||||
// 不兼容的插件不允许更新
|
||||
if (maimaiVersion && !checkPluginCompatibility(plugin)) {
|
||||
toast({
|
||||
title: '无法更新',
|
||||
description: getIncompatibleReason(plugin) ?? '插件与当前麦麦版本不兼容',
|
||||
variant: 'destructive',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const updateResult = await updatePlugin(
|
||||
plugin.id,
|
||||
@@ -833,6 +878,7 @@ function PluginsPageContent() {
|
||||
checkPluginCompatibility={checkPluginCompatibility}
|
||||
needsUpdate={needsUpdate}
|
||||
getStatusBadge={getStatusBadge}
|
||||
getIncompatibleReason={getIncompatibleReason}
|
||||
/>
|
||||
) : activeTab === 'installed' ? (
|
||||
<InstalledTab
|
||||
@@ -850,6 +896,7 @@ function PluginsPageContent() {
|
||||
checkPluginCompatibility={checkPluginCompatibility}
|
||||
needsUpdate={needsUpdate}
|
||||
getStatusBadge={getStatusBadge}
|
||||
getIncompatibleReason={getIncompatibleReason}
|
||||
/>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
|
||||
Reference in New Issue
Block a user