optimize: skills page structure (#905)

This commit is contained in:
Felix
2026-04-24 10:26:33 +08:00
committed by GitHub
parent 42a26c41a2
commit ae9af725b2
4 changed files with 24 additions and 1 deletions

View File

@@ -4,7 +4,9 @@ import type {
NavItemDef, NavItemDef,
RouteDef, RouteDef,
SettingsSectionDef, SettingsSectionDef,
SkillDetailMetaProps,
} from './types'; } from './types';
import type { ComponentType } from 'react';
class RendererExtensionRegistry { class RendererExtensionRegistry {
private extensions: RendererExtension[] = []; private extensions: RendererExtension[] = [];
@@ -56,6 +58,10 @@ class RendererExtensionRegistry {
.sort((a, b) => (a.order ?? 100) - (b.order ?? 100)); .sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
} }
getSkillDetailMetaComponents(): ComponentType<SkillDetailMetaProps>[] {
return this.extensions.flatMap((ext) => ext.skills?.detailMetaComponents ?? []);
}
async initializeAll(): Promise<void> { async initializeAll(): Promise<void> {
for (const ext of this.extensions) { for (const ext of this.extensions) {
try { try {

View File

@@ -1,4 +1,5 @@
import type { ComponentType } from 'react'; import type { ComponentType } from 'react';
import type { Skill } from '../types/skill';
export interface NavItemDef { export interface NavItemDef {
to: string; to: string;
@@ -37,11 +38,21 @@ export interface SettingsSectionExtension {
sections: SettingsSectionDef[]; sections: SettingsSectionDef[];
} }
export interface SkillDetailMetaProps {
skill: Skill;
}
export interface SkillsExtension {
id: string;
detailMetaComponents?: ComponentType<SkillDetailMetaProps>[];
}
export interface RendererExtension { export interface RendererExtension {
id: string; id: string;
sidebar?: SidebarExtension; sidebar?: SidebarExtension;
routes?: RouteExtension; routes?: RouteExtension;
settings?: SettingsSectionExtension; settings?: SettingsSectionExtension;
skills?: SkillsExtension;
i18nResources?: I18nResources; i18nResources?: I18nResources;
setup?(): void | Promise<void>; setup?(): void | Promise<void>;
teardown?(): void; teardown?(): void;

View File

@@ -33,6 +33,7 @@ import { hostApiFetch } from '@/lib/host-api';
import { trackUiEvent } from '@/lib/telemetry'; import { trackUiEvent } from '@/lib/telemetry';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { Skill } from '@/types/skill'; import type { Skill } from '@/types/skill';
import { rendererExtensionRegistry } from '@/extensions/registry';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
@@ -73,6 +74,7 @@ function SkillDetailDialog({ skill, isOpen, onClose, onToggle, onUninstall, onOp
const [envVars, setEnvVars] = useState<Array<{ key: string; value: string }>>([]); const [envVars, setEnvVars] = useState<Array<{ key: string; value: string }>>([]);
const [apiKey, setApiKey] = useState(''); const [apiKey, setApiKey] = useState('');
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const detailMetaComponents = rendererExtensionRegistry.getSkillDetailMetaComponents();
// Initialize config from skill // Initialize config from skill
useEffect(() => { useEffect(() => {
@@ -206,13 +208,16 @@ function SkillDetailDialog({ skill, isOpen, onClose, onToggle, onUninstall, onOp
<h2 className="text-[28px] font-serif text-foreground font-normal mb-3 text-center tracking-tight"> <h2 className="text-[28px] font-serif text-foreground font-normal mb-3 text-center tracking-tight">
{skill.name} {skill.name}
</h2> </h2>
<div className="flex items-center justify-center gap-2.5 mb-6 opacity-80"> <div data-skill-detail-meta-row="1" className="flex items-center justify-center gap-2.5 mb-6 opacity-80">
<Badge variant="secondary" className="font-mono text-[11px] font-medium px-3 py-0.5 rounded-full bg-black/[0.04] dark:bg-white/[0.08] hover:bg-black/[0.08] dark:hover:bg-white/[0.12] border-0 shadow-none text-foreground/70 transition-colors"> <Badge variant="secondary" className="font-mono text-[11px] font-medium px-3 py-0.5 rounded-full bg-black/[0.04] dark:bg-white/[0.08] hover:bg-black/[0.08] dark:hover:bg-white/[0.12] border-0 shadow-none text-foreground/70 transition-colors">
v{skill.version} v{skill.version}
</Badge> </Badge>
<Badge variant="secondary" className="font-mono text-[11px] font-medium px-3 py-0.5 rounded-full bg-black/[0.04] dark:bg-white/[0.08] hover:bg-black/[0.08] dark:hover:bg-white/[0.12] border-0 shadow-none text-foreground/70 transition-colors"> <Badge variant="secondary" className="font-mono text-[11px] font-medium px-3 py-0.5 rounded-full bg-black/[0.04] dark:bg-white/[0.08] hover:bg-black/[0.08] dark:hover:bg-white/[0.12] border-0 shadow-none text-foreground/70 transition-colors">
{skill.isCore ? t('detail.coreSystem') : skill.isBundled ? t('detail.bundled') : t('detail.userInstalled')} {skill.isCore ? t('detail.coreSystem') : skill.isBundled ? t('detail.bundled') : t('detail.userInstalled')}
</Badge> </Badge>
{detailMetaComponents.map((DetailMetaComponent, index) => (
<DetailMetaComponent key={`skill-detail-meta-${index}`} skill={skill} />
))}
</div> </div>
{skill.description && ( {skill.description && (

View File

@@ -86,6 +86,7 @@ export default defineConfig({
'@': resolve(__dirname, 'src'), '@': resolve(__dirname, 'src'),
'@electron': resolve(__dirname, 'electron'), '@electron': resolve(__dirname, 'electron'),
}, },
dedupe: ['react', 'react-dom', 'react-i18next', 'zustand', 'sonner', 'lucide-react'],
}, },
server: { server: {
port: 5173, port: 5173,