feat(desktop): embed BoosAPI Desktop client as a subproject

Vendor the rebranded BoosAPI Desktop client (Tauri + Vue) into metapi as
a monorepo subdirectory under desktop/. Source only — node_modules,
dist, Cargo target and the upstream .git history are excluded.

desktop/ keeps its own independent Cargo workspace (desktop/src-tauri
with crates/*); it is not part of the backend boosapi crate, so a
`cargo check` at the metapi root still compiles only the backend. Build
the desktop app from desktop/src-tauri per desktop/README.md.

Note: app icons are still placeholder assets pending the final BoosAPI
logo. The updater signing keys live outside the repo and are not
included.
This commit is contained in:
boos
2026-06-22 11:32:57 +08:00
parent d9c78ecc73
commit e0a5f57793
2305 changed files with 449355 additions and 0 deletions
@@ -0,0 +1,76 @@
import React from 'react';
import { Avatar, theme } from 'antd';
import { Globe, FileSearch, Plug } from 'lucide-react';
import { useResolvedAvatarSrc } from '@/hooks/useResolvedAvatarSrc';
import type { McpServer } from '@/types';
/** Icons for builtin MCP servers, keyed by server name. Returns icon at given size. */
const BUILTIN_ICON_FACTORY: Record<string, (size: number) => React.ReactNode> = {
'@boosapi/fetch': (s) => <Globe size={s} />,
'@boosapi/search-file': (s) => <FileSearch size={s} />,
};
/** Static 16px icons for external use. */
export const BUILTIN_ICONS: Record<string, React.ReactNode> = {
'@boosapi/fetch': <Globe size={16} />,
'@boosapi/search-file': <FileSearch size={16} />,
};
/**
* Renders the appropriate icon for an MCP server:
* - Builtin servers → fixed icon from BUILTIN_ICONS
* - Custom servers with emoji/url/file iconType → user-chosen icon
* - Default → Plug icon
*/
export function McpServerIcon({ server, size = 24 }: { server: McpServer; size?: number }) {
const resolvedSrc = useResolvedAvatarSrc(
(server.iconType as 'icon' | 'emoji' | 'url' | 'file') ?? 'icon',
server.iconValue ?? '',
);
const { token } = theme.useToken();
// Builtin servers: use fixed icon
if (server.source === 'builtin' && BUILTIN_ICON_FACTORY[server.name]) {
return (
<span style={{
width: size, height: size,
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
flexShrink: 0, color: token.colorTextSecondary,
}}>
{BUILTIN_ICON_FACTORY[server.name](size * 0.7)}
</span>
);
}
// Custom: emoji
if (server.iconType === 'emoji' && server.iconValue) {
return (
<span style={{
width: size, height: size, borderRadius: '50%',
backgroundColor: token.colorFillSecondary,
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
fontSize: size * 0.6, lineHeight: 1, flexShrink: 0,
}}>
{server.iconValue}
</span>
);
}
// Custom: url or file image
if ((server.iconType === 'url' || server.iconType === 'file') && server.iconValue) {
const src = server.iconType === 'file' ? resolvedSrc : server.iconValue;
return <Avatar size={size} src={src} style={{ flexShrink: 0 }} />;
}
// Default: plug icon
return (
<span style={{
width: size, height: size, borderRadius: '50%',
backgroundColor: token.colorFillSecondary,
display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
flexShrink: 0, color: token.colorTextSecondary,
}}>
<Plug size={size * 0.6} />
</span>
);
}