feat: 更新 CodeEditor 组件,重构为懒加载并添加 CodeEditorImpl,优化导入路径

This commit is contained in:
DrSmoothl
2026-04-24 23:10:01 +08:00
parent 3b6d30cd5e
commit 201efe66a1
11 changed files with 234 additions and 188 deletions

View File

@@ -6,7 +6,7 @@ import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint' import tseslint from 'typescript-eslint'
export default tseslint.config( export default tseslint.config(
{ ignores: ['dist'] }, { ignores: ['dist', 'out'] },
jsxA11y.flatConfigs.recommended, jsxA11y.flatConfigs.recommended,
{ {
extends: [js.configs.recommended, ...tseslint.configs.recommended], extends: [js.configs.recommended, ...tseslint.configs.recommended],
@@ -25,10 +25,7 @@ export default tseslint.config(
acc[key] = 'warn' acc[key] = 'warn'
return acc return acc
}, {}), }, {}),
'react-refresh/only-export-components': [ 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'warn',
{ allowConstantExport: true },
],
// 关闭或降级其他规则 // 关闭或降级其他规则
'@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': 'warn', '@typescript-eslint/no-unused-vars': 'warn',
@@ -37,4 +34,11 @@ export default tseslint.config(
'jsx-a11y/no-autofocus': 'warn', 'jsx-a11y/no-autofocus': 'warn',
}, },
}, },
{
files: ['**/*.d.ts'],
rules: {
// Ambient global declarations use `var` in TypeScript declaration files.
'no-var': 'off',
},
}
) )

View File

@@ -1,19 +1,8 @@
import { useEffect, useState } from 'react' import { lazy, Suspense } from 'react'
import CodeMirror from '@uiw/react-codemirror'
import { css } from '@codemirror/lang-css'
import { json, jsonParseLinter } from '@codemirror/lang-json'
import { linter } from '@codemirror/lint'
import { python } from '@codemirror/lang-python'
import { oneDark } from '@codemirror/theme-one-dark'
import { EditorView } from '@codemirror/view'
import { StreamLanguage } from '@codemirror/language'
import { toml as tomlMode } from '@codemirror/legacy-modes/mode/toml'
import { useTheme } from '@/components/use-theme'
export type Language = 'python' | 'json' | 'toml' | 'css' | 'text' export type Language = 'python' | 'json' | 'toml' | 'css' | 'text'
interface CodeEditorProps { export interface CodeEditorProps {
value: string value: string
onChange?: (value: string) => void onChange?: (value: string) => void
@@ -27,109 +16,38 @@ interface CodeEditorProps {
className?: string className?: string
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any const CodeEditorImpl = lazy(() => import('./CodeEditorImpl'))
const languageExtensions: Record<Language, any[]> = {
python: [python()],
json: [json(), linter(jsonParseLinter())],
toml: [StreamLanguage.define(tomlMode)],
css: [css()],
text: [],
}
export function CodeEditor({ function CodeEditorFallback({
value, height,
onChange,
language = 'text',
readOnly = false,
height = '400px',
minHeight, minHeight,
maxHeight, maxHeight,
placeholder,
theme,
className = '', className = '',
}: CodeEditorProps) { }: Pick<CodeEditorProps, 'height' | 'minHeight' | 'maxHeight' | 'className'>) {
const [mounted, setMounted] = useState(false) return (
const { resolvedTheme } = useTheme() <div
className={`bg-muted animate-pulse rounded-md border ${className}`}
style={{ height, minHeight, maxHeight }}
/>
)
}
useEffect(() => { export function CodeEditor(props: CodeEditorProps) {
setMounted(true) const { height = '400px', minHeight, maxHeight, className = '' } = props
}, [])
if (!mounted) {
return (
<div
className={`rounded-md border bg-muted animate-pulse ${className}`}
style={{ height, minHeight, maxHeight }}
/>
)
}
const extensions = [
...(languageExtensions[language] || []),
EditorView.lineWrapping,
// 应用 JetBrains Mono 字体
EditorView.theme({
'&': {
fontFamily: '"JetBrains Mono", "Fira Code", "Consolas", "Monaco", monospace',
},
'.cm-content': {
fontFamily: '"JetBrains Mono", "Fira Code", "Consolas", "Monaco", monospace',
},
'.cm-gutters': {
fontFamily: '"JetBrains Mono", "Fira Code", "Consolas", "Monaco", monospace',
},
'.cm-scroller': {
fontFamily: '"JetBrains Mono", "Fira Code", "Consolas", "Monaco", monospace',
},
}),
]
if (readOnly) {
extensions.push(EditorView.editable.of(false))
}
// 如果外部传了 theme prop 则使用,否则从 context 自动获取
const effectiveTheme = theme ?? resolvedTheme
return ( return (
<div className={`rounded-md overflow-hidden border custom-scrollbar ${className}`}> <Suspense
<CodeMirror fallback={
value={value} <CodeEditorFallback
height={height} height={height}
minHeight={minHeight} minHeight={minHeight}
maxHeight={maxHeight} maxHeight={maxHeight}
theme={effectiveTheme === 'dark' ? oneDark : undefined} className={className}
extensions={extensions} />
onChange={onChange} }
placeholder={placeholder} >
basicSetup={{ <CodeEditorImpl {...props} />
lineNumbers: true, </Suspense>
highlightActiveLineGutter: true,
highlightSpecialChars: true,
history: true,
foldGutter: true,
drawSelection: true,
dropCursor: true,
allowMultipleSelections: true,
indentOnInput: true,
syntaxHighlighting: true,
bracketMatching: true,
closeBrackets: true,
autocompletion: true,
rectangularSelection: true,
crosshairCursor: true,
highlightActiveLine: true,
highlightSelectionMatches: true,
closeBracketsKeymap: true,
defaultKeymap: true,
searchKeymap: true,
historyKeymap: true,
foldKeymap: true,
completionKeymap: true,
lintKeymap: true,
}}
/>
</div>
) )
} }

View File

@@ -0,0 +1,105 @@
import { css } from '@codemirror/lang-css'
import { json, jsonParseLinter } from '@codemirror/lang-json'
import { python } from '@codemirror/lang-python'
import { StreamLanguage } from '@codemirror/language'
import { toml as tomlMode } from '@codemirror/legacy-modes/mode/toml'
import { linter } from '@codemirror/lint'
import { oneDark } from '@codemirror/theme-one-dark'
import { EditorView } from '@codemirror/view'
import CodeMirror from '@uiw/react-codemirror'
import { useTheme } from '@/components/use-theme'
import type { CodeEditorProps, Language } from './CodeEditor'
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const languageExtensions: Record<Language, any[]> = {
python: [python()],
json: [json(), linter(jsonParseLinter())],
toml: [StreamLanguage.define(tomlMode)],
css: [css()],
text: [],
}
export default function CodeEditorImpl({
value,
onChange,
language = 'text',
readOnly = false,
height = '400px',
minHeight,
maxHeight,
placeholder,
theme,
className = '',
}: CodeEditorProps) {
const { resolvedTheme } = useTheme()
const extensions = [
...(languageExtensions[language] || []),
EditorView.lineWrapping,
// 应用 JetBrains Mono 字体
EditorView.theme({
'&': {
fontFamily: '"JetBrains Mono", "Fira Code", "Consolas", "Monaco", monospace',
},
'.cm-content': {
fontFamily: '"JetBrains Mono", "Fira Code", "Consolas", "Monaco", monospace',
},
'.cm-gutters': {
fontFamily: '"JetBrains Mono", "Fira Code", "Consolas", "Monaco", monospace',
},
'.cm-scroller': {
fontFamily: '"JetBrains Mono", "Fira Code", "Consolas", "Monaco", monospace',
},
}),
]
if (readOnly) {
extensions.push(EditorView.editable.of(false))
}
// 如果外部传了 theme prop 则使用,否则从 context 自动获取
const effectiveTheme = theme ?? resolvedTheme
return (
<div className={`custom-scrollbar overflow-hidden rounded-md border ${className}`}>
<CodeMirror
value={value}
height={height}
minHeight={minHeight}
maxHeight={maxHeight}
theme={effectiveTheme === 'dark' ? oneDark : undefined}
extensions={extensions}
onChange={onChange}
placeholder={placeholder}
basicSetup={{
lineNumbers: true,
highlightActiveLineGutter: true,
highlightSpecialChars: true,
history: true,
foldGutter: true,
drawSelection: true,
dropCursor: true,
allowMultipleSelections: true,
indentOnInput: true,
syntaxHighlighting: true,
bracketMatching: true,
closeBrackets: true,
autocompletion: true,
rectangularSelection: true,
crosshairCursor: true,
highlightActiveLine: true,
highlightSelectionMatches: true,
closeBracketsKeymap: true,
defaultKeymap: true,
searchKeymap: true,
historyKeymap: true,
foldKeymap: true,
completionKeymap: true,
lintKeymap: true,
}}
/>
</div>
)
}

View File

@@ -1443,6 +1443,7 @@ export function ExpressionReviewer({ open, onOpenChange }: ExpressionReviewerPro
<div <div
className="relative w-full max-w-md h-[400px] flex items-center justify-center" className="relative w-full max-w-md h-[400px] flex items-center justify-center"
role="listbox" role="listbox"
tabIndex={0}
aria-label="待审核的表达方式" aria-label="待审核的表达方式"
aria-activedescendant={quickExpressions[quickCurrentIndex] ? `quick-expr-${quickExpressions[quickCurrentIndex].id}` : undefined} aria-activedescendant={quickExpressions[quickCurrentIndex] ? `quick-expr-${quickExpressions[quickCurrentIndex].id}` : undefined}
> >
@@ -1561,14 +1562,14 @@ if (isCurrent) {
</div> </div>
{/* 情景 */} {/* 情景 */}
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider"></label> <div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider"></div>
<div className="p-3 bg-muted/30 rounded-lg border border-border/50"> <div className="p-3 bg-muted/30 rounded-lg border border-border/50">
<p className="text-lg font-medium leading-relaxed">{expr.situation}</p> <p className="text-lg font-medium leading-relaxed">{expr.situation}</p>
</div> </div>
</div> </div>
{/* 风格 */} {/* 风格 */}
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider"></label> <div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider"></div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{expr.style.split(/[,]/).map((s, i) => ( {expr.style.split(/[,]/).map((s, i) => (
<Badge key={i} variant="secondary" className="font-normal"> <Badge key={i} variant="secondary" className="font-normal">
@@ -1614,14 +1615,14 @@ if (isCurrent) {
</div> </div>
{/* 情景 */} {/* 情景 */}
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider"></label> <div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider"></div>
<div className="p-3 bg-muted/30 rounded-lg border border-border/50"> <div className="p-3 bg-muted/30 rounded-lg border border-border/50">
<p className="text-lg font-medium leading-relaxed">{expr.situation}</p> <p className="text-lg font-medium leading-relaxed">{expr.situation}</p>
</div> </div>
</div> </div>
{/* 风格 */} {/* 风格 */}
<div className="space-y-1.5"> <div className="space-y-1.5">
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider"></label> <div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider"></div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{expr.style.split(/[,]/).map((s, i) => ( {expr.style.split(/[,]/).map((s, i) => (
<Badge key={i} variant="secondary" className="font-normal"> <Badge key={i} variant="secondary" className="font-normal">

View File

@@ -1,6 +1,6 @@
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { ListFieldEditor } from '@/components' import { ListFieldEditor } from '@/components/ListFieldEditor'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label' import { Label } from '@/components/ui/label'

View File

@@ -1,31 +1,13 @@
import { createRootRoute, createRoute, createRouter, Outlet, redirect } from '@tanstack/react-router' import {
createRootRoute,
createRoute,
createRouter,
lazyRouteComponent,
Outlet,
redirect,
} from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/router-devtools' import { TanStackRouterDevtools } from '@tanstack/router-devtools'
import { IndexPage } from './routes/index'
import { SettingsPage } from './routes/settings'
import { AuthPage } from './routes/auth'
import { SetupPage } from './routes/setup'
import { NotFoundPage } from './routes/404' import { NotFoundPage } from './routes/404'
import { BotConfigPage } from './routes/config/bot'
import { ModelProviderConfigPage } from './routes/config/modelProvider'
import { ModelConfigPage } from './routes/config/model'
import { AdapterConfigPage } from './routes/config/adapter'
import { EmojiManagementPage } from './routes/resource/emoji'
import { ExpressionManagementPage } from './routes/resource/expression'
import { JargonManagementPage } from './routes/resource/jargon'
import { PersonManagementPage } from './routes/person'
import { KnowledgeGraphPage } from './routes/resource/knowledge-graph'
import { KnowledgeBasePage } from './routes/resource/knowledge-base'
import { LogViewerPage } from './routes/logs'
import { PlannerMonitorPage } from './routes/monitor'
import { PluginsPage } from './routes/plugins'
import { ModelPresetsPage } from './routes/model-presets'
import { PluginConfigPage } from './routes/plugin-config'
import { PluginMirrorsPage } from './routes/plugin-mirrors'
import { PluginDetailPage } from './routes/plugin-detail'
import { ChatPage } from './routes/chat/index'
import { WebUIFeedbackSurveyPage, MaiBotFeedbackSurveyPage } from './routes/survey'
import PackMarketPage from './routes/config/pack-market'
import PackDetailPage from './routes/config/pack-detail'
import { Layout } from './components/layout' import { Layout } from './components/layout'
import { checkAuth } from './hooks/use-auth' import { checkAuth } from './hooks/use-auth'
import { RouteErrorBoundary } from './components/error-boundary' import { RouteErrorBoundary } from './components/error-boundary'
@@ -50,14 +32,14 @@ const rootRoute = createRootRoute({
const authRoute = createRoute({ const authRoute = createRoute({
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
path: '/auth', path: '/auth',
component: AuthPage, component: lazyRouteComponent(() => import('./routes/auth'), 'AuthPage'),
}) })
// 首次配置路由(无 Layout // 首次配置路由(无 Layout
const setupRoute = createRoute({ const setupRoute = createRoute({
getParentRoute: () => rootRoute, getParentRoute: () => rootRoute,
path: '/setup', path: '/setup',
component: SetupPage, component: lazyRouteComponent(() => import('./routes/setup/index.tsx'), 'SetupPage'),
}) })
// 受保护的路由 Root带 Layout // 受保护的路由 Root带 Layout
@@ -76,168 +58,192 @@ const protectedRoute = createRoute({
const indexRoute = createRoute({ const indexRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/', path: '/',
component: IndexPage, component: lazyRouteComponent(() => import('./routes/index'), 'IndexPage'),
}) })
// 配置路由 - 麦麦主程序配置 // 配置路由 - 麦麦主程序配置
const botConfigRoute = createRoute({ const botConfigRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/config/bot', path: '/config/bot',
component: BotConfigPage, component: lazyRouteComponent(() => import('./routes/config/bot'), 'BotConfigPage'),
}) })
// 配置路由 - 麦麦模型提供商配置 // 配置路由 - 麦麦模型提供商配置
const modelProviderConfigRoute = createRoute({ const modelProviderConfigRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/config/modelProvider', path: '/config/modelProvider',
component: ModelProviderConfigPage, component: lazyRouteComponent(
() => import('./routes/config/modelProvider/index.tsx'),
'ModelProviderConfigPage'
),
}) })
// 配置路由 - 麦麦模型配置 // 配置路由 - 麦麦模型配置
const modelConfigRoute = createRoute({ const modelConfigRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/config/model', path: '/config/model',
component: ModelConfigPage, component: lazyRouteComponent(() => import('./routes/config/model'), 'ModelConfigPage'),
}) })
// 配置路由 - 麦麦适配器配置 // 配置路由 - 麦麦适配器配置
const adapterConfigRoute = createRoute({ const adapterConfigRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/config/adapter', path: '/config/adapter',
component: AdapterConfigPage, component: lazyRouteComponent(() => import('./routes/config/adapter'), 'AdapterConfigPage'),
}) })
// 资源管理路由 - 表情包管理 // 资源管理路由 - 表情包管理
const emojiManagementRoute = createRoute({ const emojiManagementRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/resource/emoji', path: '/resource/emoji',
component: EmojiManagementPage, component: lazyRouteComponent(
() => import('./routes/resource/emoji/index.tsx'),
'EmojiManagementPage'
),
}) })
// 资源管理路由 - 表达方式管理 // 资源管理路由 - 表达方式管理
const expressionManagementRoute = createRoute({ const expressionManagementRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/resource/expression', path: '/resource/expression',
component: ExpressionManagementPage, component: lazyRouteComponent(
() => import('./routes/resource/expression/index.tsx'),
'ExpressionManagementPage'
),
}) })
// 资源管理路由 - 人物信息管理 // 资源管理路由 - 人物信息管理
const personManagementRoute = createRoute({ const personManagementRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/resource/person', path: '/resource/person',
component: PersonManagementPage, component: lazyRouteComponent(() => import('./routes/person'), 'PersonManagementPage'),
}) })
// 资源管理路由 - 黑话管理 // 资源管理路由 - 黑话管理
const jargonManagementRoute = createRoute({ const jargonManagementRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/resource/jargon', path: '/resource/jargon',
component: JargonManagementPage, component: lazyRouteComponent(
() => import('./routes/resource/jargon/index.tsx'),
'JargonManagementPage'
),
}) })
// 资源管理路由 - 知识库图谱可视化 // 资源管理路由 - 知识库图谱可视化
const knowledgeGraphRoute = createRoute({ const knowledgeGraphRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/resource/knowledge-graph', path: '/resource/knowledge-graph',
component: KnowledgeGraphPage, component: lazyRouteComponent(
() => import('./routes/resource/knowledge-graph/index.tsx'),
'KnowledgeGraphPage'
),
}) })
// 资源管理路由 - 麦麦知识库管理 // 资源管理路由 - 麦麦知识库管理
const knowledgeBaseRoute = createRoute({ const knowledgeBaseRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/resource/knowledge-base', path: '/resource/knowledge-base',
component: KnowledgeBasePage, component: lazyRouteComponent(
() => import('./routes/resource/knowledge-base'),
'KnowledgeBasePage'
),
}) })
// 日志查看器路由 // 日志查看器路由
const logsRoute = createRoute({ const logsRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/logs', path: '/logs',
component: LogViewerPage, component: lazyRouteComponent(() => import('./routes/logs'), 'LogViewerPage'),
}) })
// MaiSaka 聊天流监控路由 // MaiSaka 聊天流监控路由
const plannerMonitorRoute = createRoute({ const plannerMonitorRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/planner-monitor', path: '/planner-monitor',
component: PlannerMonitorPage, component: lazyRouteComponent(() => import('./routes/monitor/index.tsx'), 'PlannerMonitorPage'),
}) })
// 本地聊天室路由 // 本地聊天室路由
const chatRoute = createRoute({ const chatRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/chat', path: '/chat',
component: ChatPage, component: lazyRouteComponent(() => import('./routes/chat/index'), 'ChatPage'),
}) })
// 插件市场路由 // 插件市场路由
const pluginsRoute = createRoute({ const pluginsRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/plugins', path: '/plugins',
component: PluginsPage, component: lazyRouteComponent(() => import('./routes/plugins/index'), 'PluginsPage'),
}) })
// 插件详情路由 // 插件详情路由
const pluginDetailRoute = createRoute({ const pluginDetailRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/plugin-detail', path: '/plugin-detail',
component: PluginDetailPage, component: lazyRouteComponent(() => import('./routes/plugin-detail'), 'PluginDetailPage'),
}) })
// 模型分配预设市场路由 // 模型分配预设市场路由
const modelPresetsRoute = createRoute({ const modelPresetsRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/model-presets', path: '/model-presets',
component: ModelPresetsPage, component: lazyRouteComponent(() => import('./routes/model-presets'), 'ModelPresetsPage'),
}) })
// 插件配置路由 // 插件配置路由
const pluginConfigRoute = createRoute({ const pluginConfigRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/plugin-config', path: '/plugin-config',
component: PluginConfigPage, component: lazyRouteComponent(() => import('./routes/plugin-config'), 'PluginConfigPage'),
}) })
// 插件镜像源配置路由 // 插件镜像源配置路由
const pluginMirrorsRoute = createRoute({ const pluginMirrorsRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/plugin-mirrors', path: '/plugin-mirrors',
component: PluginMirrorsPage, component: lazyRouteComponent(() => import('./routes/plugin-mirrors'), 'PluginMirrorsPage'),
}) })
// 设置页路由 // 设置页路由
const settingsRoute = createRoute({ const settingsRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/settings', path: '/settings',
component: SettingsPage, component: lazyRouteComponent(() => import('./routes/settings/index.tsx'), 'SettingsPage'),
}) })
// 配置模板市场路由 // 配置模板市场路由
const packMarketRoute = createRoute({ const packMarketRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/config/pack-market', path: '/config/pack-market',
component: PackMarketPage, component: lazyRouteComponent(() => import('./routes/config/pack-market')),
}) })
// 配置模板详情路由 // 配置模板详情路由
export const packDetailRoute = createRoute({ export const packDetailRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/config/pack-market/$packId', path: '/config/pack-market/$packId',
component: PackDetailPage, component: lazyRouteComponent(() => import('./routes/config/pack-detail')),
}) })
// 问卷调查路由 - WebUI 反馈 // 问卷调查路由 - WebUI 反馈
const webuiFeedbackSurveyRoute = createRoute({ const webuiFeedbackSurveyRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/survey/webui-feedback', path: '/survey/webui-feedback',
component: WebUIFeedbackSurveyPage, component: lazyRouteComponent(
() => import('./routes/survey/webui-feedback'),
'WebUIFeedbackSurveyPage'
),
}) })
// 问卷调查路由 - 麦麦体验反馈 // 问卷调查路由 - 麦麦体验反馈
const maibotFeedbackSurveyRoute = createRoute({ const maibotFeedbackSurveyRoute = createRoute({
getParentRoute: () => protectedRoute, getParentRoute: () => protectedRoute,
path: '/survey/maibot-feedback', path: '/survey/maibot-feedback',
component: MaiBotFeedbackSurveyPage, component: lazyRouteComponent(
() => import('./routes/survey/maibot-feedback'),
'MaiBotFeedbackSurveyPage'
),
}) })
// 404 路由 // 404 路由
@@ -294,7 +300,7 @@ function collectRoutePaths(node: RouteNode): string[] {
export const registeredRoutePaths = new Set(collectRoutePaths(routeTree as RouteNode)) export const registeredRoutePaths = new Set(collectRoutePaths(routeTree as RouteNode))
// 创建路由器 // 创建路由器
export const router = createRouter({ export const router = createRouter({
routeTree, routeTree,
defaultNotFoundComponent: NotFoundPage, defaultNotFoundComponent: NotFoundPage,
defaultErrorComponent: ({ error }) => <RouteErrorBoundary error={error} />, defaultErrorComponent: ({ error }) => <RouteErrorBoundary error={error} />,

View File

@@ -16,7 +16,7 @@ import {
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { ScrollArea } from '@/components/ui/scroll-area' import { ScrollArea } from '@/components/ui/scroll-area'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { CodeEditor } from '@/components' import { CodeEditor } from '@/components/CodeEditor'
import { DynamicConfigForm } from '@/components/dynamic-form' import { DynamicConfigForm } from '@/components/dynamic-form'
import { RestartOverlay } from '@/components/restart-overlay' import { RestartOverlay } from '@/components/restart-overlay'
import { useToast } from '@/hooks/use-toast' import { useToast } from '@/hooks/use-toast'

View File

@@ -12,7 +12,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
import { ListFieldEditor } from '@/components/ListFieldEditor' import { ListFieldEditor } from '@/components/ListFieldEditor'
import { Alert, AlertDescription } from '@/components/ui/alert' import { Alert, AlertDescription } from '@/components/ui/alert'
import { CodeEditor } from '@/components' import { CodeEditor } from '@/components/CodeEditor'
import { parse as parseToml } from 'smol-toml' import { parse as parseToml } from 'smol-toml'
import { import {
Select, Select,

View File

@@ -37,8 +37,8 @@ import {
type GitStatus, type GitStatus,
type MaimaiVersion, type MaimaiVersion,
} from '@/lib/plugin-api' } from '@/lib/plugin-api'
import { MarkdownRenderer } from '@/components/markdown-renderer'
import { PluginStats } from '@/components/plugin-stats' import { PluginStats } from '@/components/plugin-stats'
import { MarkdownRenderer } from '@/components'
import { recordPluginDownload } from '@/lib/plugin-stats' import { recordPluginDownload } from '@/lib/plugin-stats'
// 分类名称映射 // 分类名称映射

View File

@@ -16,7 +16,7 @@ import {
Upload, Upload,
} from 'lucide-react' } from 'lucide-react'
import { CodeEditor } from '@/components' import { CodeEditor } from '@/components/CodeEditor'
import { MemoryDeleteDialog } from '@/components/memory/MemoryDeleteDialog' import { MemoryDeleteDialog } from '@/components/memory/MemoryDeleteDialog'
import { MemoryConfigEditor } from '@/components/memory/MemoryConfigEditor' import { MemoryConfigEditor } from '@/components/memory/MemoryConfigEditor'
import { Alert, AlertDescription } from '@/components/ui/alert' import { Alert, AlertDescription } from '@/components/ui/alert'

View File

@@ -171,10 +171,14 @@ export function NodeDetailDialog({
</Button> </Button>
{onDeleteEntity ? ( {onDeleteEntity ? (
<div className="flex flex-col items-end gap-2 rounded-lg border bg-background p-3"> <div className="flex flex-col items-end gap-2 rounded-lg border bg-background p-3">
<label className="flex items-center gap-2 text-xs text-muted-foreground"> <div className="flex items-center gap-2 text-xs text-muted-foreground">
<Checkbox checked={includeParagraphs} onCheckedChange={(checked) => setIncludeParagraphs(Boolean(checked))} /> <Checkbox
checked={includeParagraphs}
</label> onCheckedChange={(checked) => setIncludeParagraphs(Boolean(checked))}
aria-label="删除该实体相关证据段落"
/>
<span></span>
</div>
<Button variant="outline" onClick={() => onDeleteEntity({ includeParagraphs })}> <Button variant="outline" onClick={() => onDeleteEntity({ includeParagraphs })}>
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" />
@@ -280,10 +284,14 @@ export function EdgeDetailDialog({
</Button> </Button>
{onDeleteEdgeGroup ? ( {onDeleteEdgeGroup ? (
<div className="flex flex-col items-end gap-2 rounded-lg border bg-background p-3"> <div className="flex flex-col items-end gap-2 rounded-lg border bg-background p-3">
<label className="flex items-center gap-2 text-xs text-muted-foreground"> <div className="flex items-center gap-2 text-xs text-muted-foreground">
<Checkbox checked={includeParagraphs} onCheckedChange={(checked) => setIncludeParagraphs(Boolean(checked))} /> <Checkbox
checked={includeParagraphs}
</label> onCheckedChange={(checked) => setIncludeParagraphs(Boolean(checked))}
aria-label="同时删除支撑段落"
/>
<span></span>
</div>
<Button variant="outline" onClick={() => onDeleteEdgeGroup({ includeParagraphs })}> <Button variant="outline" onClick={() => onDeleteEdgeGroup({ includeParagraphs })}>
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" />
@@ -371,10 +379,14 @@ export function RelationDetailDialog({
{onDeleteRelation ? ( {onDeleteRelation ? (
<div className="rounded-lg border bg-background p-3"> <div className="rounded-lg border bg-background p-3">
<label className="flex items-center gap-2 text-xs text-muted-foreground"> <div className="flex items-center gap-2 text-xs text-muted-foreground">
<Checkbox checked={includeParagraphs} onCheckedChange={(checked) => setIncludeParagraphs(Boolean(checked))} /> <Checkbox
checked={includeParagraphs}
</label> onCheckedChange={(checked) => setIncludeParagraphs(Boolean(checked))}
aria-label="同时删除支撑该关系的段落"
/>
<span></span>
</div>
<Button className="mt-3" variant="outline" onClick={() => onDeleteRelation(relation, includeParagraphs)}> <Button className="mt-3" variant="outline" onClick={() => onDeleteRelation(relation, includeParagraphs)}>
<Trash2 className="mr-2 h-4 w-4" /> <Trash2 className="mr-2 h-4 w-4" />