@samanhappy/mcphub 0.0.10 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/package.json +8 -5
  2. package/.env.example +0 -2
  3. package/.eslintrc.json +0 -25
  4. package/.github/workflows/build.yml +0 -51
  5. package/.github/workflows/release.yml +0 -19
  6. package/.prettierrc +0 -7
  7. package/Dockerfile +0 -51
  8. package/assets/amap-edit.png +0 -0
  9. package/assets/amap-result.png +0 -0
  10. package/assets/cherry-mcp.png +0 -0
  11. package/assets/cursor-mcp.png +0 -0
  12. package/assets/cursor-query.png +0 -0
  13. package/assets/cursor-tools.png +0 -0
  14. package/assets/dashboard.png +0 -0
  15. package/assets/dashboard.zh.png +0 -0
  16. package/assets/group.png +0 -0
  17. package/assets/group.zh.png +0 -0
  18. package/assets/market.zh.png +0 -0
  19. package/assets/wegroup.jpg +0 -0
  20. package/assets/wegroup.png +0 -0
  21. package/assets/wexin.png +0 -0
  22. package/bin/mcphub.js +0 -3
  23. package/doc/intro.md +0 -73
  24. package/doc/intro2.md +0 -232
  25. package/entrypoint.sh +0 -10
  26. package/frontend/favicon.ico +0 -0
  27. package/frontend/index.html +0 -13
  28. package/frontend/postcss.config.js +0 -6
  29. package/frontend/src/App.tsx +0 -44
  30. package/frontend/src/components/AddGroupForm.tsx +0 -132
  31. package/frontend/src/components/AddServerForm.tsx +0 -90
  32. package/frontend/src/components/ChangePasswordForm.tsx +0 -158
  33. package/frontend/src/components/EditGroupForm.tsx +0 -149
  34. package/frontend/src/components/EditServerForm.tsx +0 -76
  35. package/frontend/src/components/GroupCard.tsx +0 -143
  36. package/frontend/src/components/MarketServerCard.tsx +0 -153
  37. package/frontend/src/components/MarketServerDetail.tsx +0 -297
  38. package/frontend/src/components/ProtectedRoute.tsx +0 -27
  39. package/frontend/src/components/ServerCard.tsx +0 -230
  40. package/frontend/src/components/ServerForm.tsx +0 -276
  41. package/frontend/src/components/icons/LucideIcons.tsx +0 -14
  42. package/frontend/src/components/layout/Content.tsx +0 -17
  43. package/frontend/src/components/layout/Header.tsx +0 -61
  44. package/frontend/src/components/layout/Sidebar.tsx +0 -98
  45. package/frontend/src/components/ui/Badge.tsx +0 -33
  46. package/frontend/src/components/ui/Button.tsx +0 -0
  47. package/frontend/src/components/ui/DeleteDialog.tsx +0 -48
  48. package/frontend/src/components/ui/Pagination.tsx +0 -128
  49. package/frontend/src/components/ui/Toast.tsx +0 -96
  50. package/frontend/src/components/ui/ToggleGroup.tsx +0 -134
  51. package/frontend/src/components/ui/ToolCard.tsx +0 -38
  52. package/frontend/src/contexts/AuthContext.tsx +0 -159
  53. package/frontend/src/contexts/ToastContext.tsx +0 -60
  54. package/frontend/src/hooks/useGroupData.ts +0 -232
  55. package/frontend/src/hooks/useMarketData.ts +0 -410
  56. package/frontend/src/hooks/useServerData.ts +0 -306
  57. package/frontend/src/hooks/useSettingsData.ts +0 -131
  58. package/frontend/src/i18n.ts +0 -42
  59. package/frontend/src/index.css +0 -20
  60. package/frontend/src/layouts/MainLayout.tsx +0 -33
  61. package/frontend/src/locales/en.json +0 -214
  62. package/frontend/src/locales/zh.json +0 -214
  63. package/frontend/src/main.tsx +0 -12
  64. package/frontend/src/pages/Dashboard.tsx +0 -206
  65. package/frontend/src/pages/GroupsPage.tsx +0 -116
  66. package/frontend/src/pages/LoginPage.tsx +0 -104
  67. package/frontend/src/pages/MarketPage.tsx +0 -356
  68. package/frontend/src/pages/ServersPage.tsx +0 -144
  69. package/frontend/src/pages/SettingsPage.tsx +0 -149
  70. package/frontend/src/services/authService.ts +0 -141
  71. package/frontend/src/types/index.ts +0 -160
  72. package/frontend/src/utils/cn.ts +0 -10
  73. package/frontend/tsconfig.json +0 -31
  74. package/frontend/tsconfig.node.json +0 -10
  75. package/frontend/vite.config.ts +0 -26
  76. package/googled76ca578b6543fbc.html +0 -1
  77. package/jest.config.js +0 -10
  78. package/mcp_settings.json +0 -45
  79. package/servers.json +0 -74722
  80. package/src/config/index.ts +0 -46
  81. package/src/controllers/authController.ts +0 -179
  82. package/src/controllers/groupController.ts +0 -341
  83. package/src/controllers/marketController.ts +0 -154
  84. package/src/controllers/serverController.ts +0 -303
  85. package/src/index.ts +0 -17
  86. package/src/middlewares/auth.ts +0 -28
  87. package/src/middlewares/index.ts +0 -43
  88. package/src/models/User.ts +0 -103
  89. package/src/routes/index.ts +0 -96
  90. package/src/server.ts +0 -72
  91. package/src/services/groupService.ts +0 -232
  92. package/src/services/marketService.ts +0 -116
  93. package/src/services/mcpService.ts +0 -385
  94. package/src/services/sseService.ts +0 -119
  95. package/src/types/index.ts +0 -129
  96. package/src/utils/migration.ts +0 -52
  97. package/tsconfig.json +0 -17
@@ -1,98 +0,0 @@
1
- import React from 'react';
2
- import { useTranslation } from 'react-i18next';
3
- import { NavLink, useLocation } from 'react-router-dom';
4
-
5
- interface SidebarProps {
6
- collapsed: boolean;
7
- }
8
-
9
- interface MenuItem {
10
- path: string;
11
- label: string;
12
- icon: React.ReactNode;
13
- }
14
-
15
- const Sidebar: React.FC<SidebarProps> = ({ collapsed }) => {
16
- const { t } = useTranslation();
17
- const location = useLocation();
18
-
19
- // Menu item configuration
20
- const menuItems: MenuItem[] = [
21
- {
22
- path: '/',
23
- label: t('nav.dashboard'),
24
- icon: (
25
- <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
26
- <path d="M2 10a8 8 0 018-8v8h8a8 8 0 11-16 0z" />
27
- <path d="M12 2.252A8.014 8.014 0 0117.748 8H12V2.252z" />
28
- </svg>
29
- ),
30
- },
31
- {
32
- path: '/servers',
33
- label: t('nav.servers'),
34
- icon: (
35
- <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
36
- <path fillRule="evenodd" d="M2 5a2 2 0 012-2h12a2 2 0 012 2v2a2 2 0 01-2 2H4a2 2 0 01-2-2V5zm14 1a1 1 0 11-2 0 1 1 0 012 0zM2 13a2 2 0 012-2h12a2 2 0 012 2v2a2 2 0 01-2 2H4a2 2 0 01-2-2v-2zm14 1a1 1 0 11-2 0 1 1 0 012 0z" clipRule="evenodd" />
37
- </svg>
38
- ),
39
- },
40
- {
41
- path: '/groups',
42
- label: t('nav.groups'),
43
- icon: (
44
- <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
45
- <path d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z" />
46
- </svg>
47
- ),
48
- },
49
- {
50
- path: '/market',
51
- label: t('nav.market'),
52
- icon: (
53
- <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
54
- <path d="M3 1a1 1 0 000 2h1.22l.305 1.222a.997.997 0 00.01.042l1.358 5.43-.893.892C3.74 11.846 4.632 14 6.414 14H15a1 1 0 000-2H6.414l1-1H14a1 1 0 00.894-.553l3-6A1 1 0 0017 3H6.28l-.31-1.243A1 1 0 005 1H3zM16 16.5a1.5 1.5 0 11-3 0 1.5 1.5 0 013 0zM6.5 18a1.5 1.5 0 100-3 1.5 1.5 0 000 3z" />
55
- </svg>
56
- ),
57
- },
58
- {
59
- path: '/settings',
60
- label: t('nav.settings'),
61
- icon: (
62
- <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
63
- <path fillRule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clipRule="evenodd" />
64
- </svg>
65
- ),
66
- },
67
- ];
68
-
69
- return (
70
- <aside
71
- className={`bg-white shadow-sm transition-all duration-300 ease-in-out ${
72
- collapsed ? 'w-16' : 'w-64'
73
- }`}
74
- >
75
- <nav className="p-3 space-y-1">
76
- {menuItems.map((item) => (
77
- <NavLink
78
- key={item.path}
79
- to={item.path}
80
- className={({ isActive }) =>
81
- `flex items-center px-3 py-2 rounded-md transition-colors ${
82
- isActive
83
- ? 'bg-blue-100 text-blue-800'
84
- : 'text-gray-700 hover:bg-gray-100'
85
- }`
86
- }
87
- end={item.path === '/'}
88
- >
89
- <span className="flex-shrink-0">{item.icon}</span>
90
- {!collapsed && <span className="ml-3">{item.label}</span>}
91
- </NavLink>
92
- ))}
93
- </nav>
94
- </aside>
95
- );
96
- };
97
-
98
- export default Sidebar;
@@ -1,33 +0,0 @@
1
- import { useTranslation } from 'react-i18next'
2
- import { ServerStatus } from '@/types'
3
-
4
- interface BadgeProps {
5
- status: ServerStatus
6
- }
7
-
8
- const Badge = ({ status }: BadgeProps) => {
9
- const { t } = useTranslation()
10
-
11
- const colors = {
12
- connecting: 'bg-yellow-100 text-yellow-800',
13
- connected: 'bg-green-100 text-green-800',
14
- disconnected: 'bg-red-100 text-red-800',
15
- }
16
-
17
- // Map status to translation keys
18
- const statusTranslations = {
19
- connected: 'status.online',
20
- disconnected: 'status.offline',
21
- connecting: 'status.connecting'
22
- }
23
-
24
- return (
25
- <span
26
- className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${colors[status]}`}
27
- >
28
- {t(statusTranslations[status] || status)}
29
- </span>
30
- )
31
- }
32
-
33
- export default Badge
File without changes
@@ -1,48 +0,0 @@
1
- import { useTranslation } from 'react-i18next'
2
-
3
- interface DeleteDialogProps {
4
- isOpen: boolean
5
- onClose: () => void
6
- onConfirm: () => void
7
- serverName: string
8
- isGroup?: boolean
9
- }
10
-
11
- const DeleteDialog = ({ isOpen, onClose, onConfirm, serverName, isGroup = false }: DeleteDialogProps) => {
12
- const { t } = useTranslation()
13
-
14
- if (!isOpen) return null
15
-
16
- return (
17
- <div className="fixed inset-0 bg-black bg-opacity-30 z-50 flex items-center justify-center p-4">
18
- <div className="bg-white rounded-lg shadow-lg max-w-md w-full">
19
- <div className="p-6">
20
- <h3 className="text-lg font-medium text-gray-900 mb-3">
21
- {isGroup ? t('groups.confirmDelete') : t('server.confirmDelete')}
22
- </h3>
23
- <p className="text-gray-500 mb-6">
24
- {isGroup
25
- ? t('groups.deleteWarning', { name: serverName })
26
- : t('server.deleteWarning', { name: serverName })}
27
- </p>
28
- <div className="flex justify-end space-x-3">
29
- <button
30
- onClick={onClose}
31
- className="px-4 py-2 text-gray-600 hover:text-gray-800"
32
- >
33
- {t('common.cancel')}
34
- </button>
35
- <button
36
- onClick={onConfirm}
37
- className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
38
- >
39
- {t('common.delete')}
40
- </button>
41
- </div>
42
- </div>
43
- </div>
44
- </div>
45
- )
46
- }
47
-
48
- export default DeleteDialog
@@ -1,128 +0,0 @@
1
- import React from 'react';
2
-
3
- interface PaginationProps {
4
- currentPage: number;
5
- totalPages: number;
6
- onPageChange: (page: number) => void;
7
- }
8
-
9
- const Pagination: React.FC<PaginationProps> = ({
10
- currentPage,
11
- totalPages,
12
- onPageChange
13
- }) => {
14
- // Generate page buttons
15
- const getPageButtons = () => {
16
- const buttons = [];
17
- const maxDisplayedPages = 5; // Maximum number of page buttons to display
18
-
19
- // Always display first page
20
- buttons.push(
21
- <button
22
- key="first"
23
- onClick={() => onPageChange(1)}
24
- className={`px-3 py-1 mx-1 rounded ${
25
- currentPage === 1
26
- ? 'bg-blue-500 text-white'
27
- : 'bg-gray-200 hover:bg-gray-300 text-gray-700'
28
- }`}
29
- >
30
- 1
31
- </button>
32
- );
33
-
34
- // Start range
35
- let startPage = Math.max(2, currentPage - Math.floor(maxDisplayedPages / 2));
36
-
37
- // If we're showing ellipsis after first page
38
- if (startPage > 2) {
39
- buttons.push(
40
- <span key="ellipsis1" className="px-3 py-1">
41
- ...
42
- </span>
43
- );
44
- }
45
-
46
- // Middle pages
47
- for (let i = startPage; i <= Math.min(totalPages - 1, startPage + maxDisplayedPages - 3); i++) {
48
- buttons.push(
49
- <button
50
- key={i}
51
- onClick={() => onPageChange(i)}
52
- className={`px-3 py-1 mx-1 rounded ${
53
- currentPage === i
54
- ? 'bg-blue-500 text-white'
55
- : 'bg-gray-200 hover:bg-gray-300 text-gray-700'
56
- }`}
57
- >
58
- {i}
59
- </button>
60
- );
61
- }
62
-
63
- // If we're showing ellipsis before last page
64
- if (startPage + maxDisplayedPages - 3 < totalPages - 1) {
65
- buttons.push(
66
- <span key="ellipsis2" className="px-3 py-1">
67
- ...
68
- </span>
69
- );
70
- }
71
-
72
- // Always display last page if there's more than one page
73
- if (totalPages > 1) {
74
- buttons.push(
75
- <button
76
- key="last"
77
- onClick={() => onPageChange(totalPages)}
78
- className={`px-3 py-1 mx-1 rounded ${
79
- currentPage === totalPages
80
- ? 'bg-blue-500 text-white'
81
- : 'bg-gray-200 hover:bg-gray-300 text-gray-700'
82
- }`}
83
- >
84
- {totalPages}
85
- </button>
86
- );
87
- }
88
-
89
- return buttons;
90
- };
91
-
92
- // If there's only one page, don't render pagination
93
- if (totalPages <= 1) {
94
- return null;
95
- }
96
-
97
- return (
98
- <div className="flex justify-center items-center my-6">
99
- <button
100
- onClick={() => onPageChange(Math.max(1, currentPage - 1))}
101
- disabled={currentPage === 1}
102
- className={`px-3 py-1 rounded mr-2 ${
103
- currentPage === 1
104
- ? 'bg-gray-100 text-gray-400 cursor-not-allowed'
105
- : 'bg-gray-200 hover:bg-gray-300 text-gray-700'
106
- }`}
107
- >
108
- &laquo; Prev
109
- </button>
110
-
111
- <div className="flex">{getPageButtons()}</div>
112
-
113
- <button
114
- onClick={() => onPageChange(Math.min(totalPages, currentPage + 1))}
115
- disabled={currentPage === totalPages}
116
- className={`px-3 py-1 rounded ml-2 ${
117
- currentPage === totalPages
118
- ? 'bg-gray-100 text-gray-400 cursor-not-allowed'
119
- : 'bg-gray-200 hover:bg-gray-300 text-gray-700'
120
- }`}
121
- >
122
- Next &raquo;
123
- </button>
124
- </div>
125
- );
126
- };
127
-
128
- export default Pagination;
@@ -1,96 +0,0 @@
1
- import React, { useEffect, useState } from 'react';
2
- import { Check, X } from 'lucide-react';
3
- import { cn } from '@/utils/cn';
4
-
5
- export type ToastType = 'success' | 'error' | 'info' | 'warning';
6
-
7
- export interface ToastProps {
8
- message: string;
9
- type?: ToastType;
10
- duration?: number;
11
- onClose: () => void;
12
- visible: boolean;
13
- }
14
-
15
- const Toast: React.FC<ToastProps> = ({
16
- message,
17
- type = 'info',
18
- duration = 3000,
19
- onClose,
20
- visible
21
- }) => {
22
- useEffect(() => {
23
- if (visible) {
24
- const timer = setTimeout(() => {
25
- onClose();
26
- }, duration);
27
-
28
- return () => clearTimeout(timer);
29
- }
30
- }, [visible, duration, onClose]);
31
-
32
- const icons = {
33
- success: <Check className="w-5 h-5 text-green-500" />,
34
- error: <X className="w-5 h-5 text-red-500" />,
35
- info: (
36
- <svg className="w-5 h-5 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
37
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
38
- </svg>
39
- ),
40
- warning: (
41
- <svg className="w-5 h-5 text-yellow-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
42
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
43
- </svg>
44
- )
45
- };
46
-
47
- const bgColors = {
48
- success: 'bg-green-50 border-green-200',
49
- error: 'bg-red-50 border-red-200',
50
- info: 'bg-blue-50 border-blue-200',
51
- warning: 'bg-yellow-50 border-yellow-200'
52
- };
53
-
54
- const textColors = {
55
- success: 'text-green-800',
56
- error: 'text-red-800',
57
- info: 'text-blue-800',
58
- warning: 'text-yellow-800'
59
- };
60
-
61
- return (
62
- <div className={cn(
63
- "fixed top-4 right-4 z-50 max-w-sm p-4 rounded-md shadow-lg border",
64
- bgColors[type],
65
- "transform transition-all duration-300 ease-in-out",
66
- visible ? "translate-x-0 opacity-100" : "translate-x-full opacity-0"
67
- )}>
68
- <div className="flex items-start">
69
- <div className="flex-shrink-0">
70
- {icons[type]}
71
- </div>
72
- <div className="ml-3">
73
- <p className={cn("text-sm font-medium", textColors[type])}>
74
- {message}
75
- </p>
76
- </div>
77
- <div className="ml-auto pl-3">
78
- <div className="-mx-1.5 -my-1.5">
79
- <button
80
- onClick={onClose}
81
- className={cn(
82
- "inline-flex rounded-md p-1.5",
83
- `hover:bg-${type}-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-${type}-500`
84
- )}
85
- >
86
- <span className="sr-only">Dismiss</span>
87
- <X className="h-5 w-5" />
88
- </button>
89
- </div>
90
- </div>
91
- </div>
92
- </div>
93
- );
94
- };
95
-
96
- export default Toast;
@@ -1,134 +0,0 @@
1
- import React, { ReactNode } from 'react';
2
- import { cn } from '@/utils/cn';
3
-
4
- interface ToggleGroupItemProps {
5
- value: string;
6
- isSelected: boolean;
7
- onClick: () => void;
8
- children: ReactNode;
9
- }
10
-
11
- export const ToggleGroupItem: React.FC<ToggleGroupItemProps> = ({
12
- value,
13
- isSelected,
14
- onClick,
15
- children
16
- }) => {
17
- return (
18
- <button
19
- type="button"
20
- role="checkbox"
21
- aria-checked={isSelected}
22
- className={cn(
23
- "flex w-full items-center justify-between p-2 rounded transition-colors cursor-pointer",
24
- isSelected
25
- ? "bg-blue-50 text-blue-700 hover:bg-blue-100 border-l-4 border-blue-500"
26
- : "hover:bg-gray-50 text-gray-700"
27
- )}
28
- onClick={onClick}
29
- >
30
- <span className="flex items-center">
31
- {children}
32
- </span>
33
- {isSelected && (
34
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-5 h-5 text-blue-500">
35
- <path fillRule="evenodd" d="M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z" clipRule="evenodd" />
36
- </svg>
37
- )}
38
- </button>
39
- );
40
- };
41
-
42
- interface ToggleGroupProps {
43
- label: string;
44
- helpText?: string;
45
- noOptionsText?: string;
46
- values: string[];
47
- options: { value: string; label: string }[];
48
- onChange: (values: string[]) => void;
49
- className?: string;
50
- }
51
-
52
- export const ToggleGroup: React.FC<ToggleGroupProps> = ({
53
- label,
54
- helpText,
55
- noOptionsText = "No options available",
56
- values,
57
- options,
58
- onChange,
59
- className
60
- }) => {
61
- const handleToggle = (value: string) => {
62
- const isSelected = values.includes(value);
63
- if (isSelected) {
64
- onChange(values.filter(v => v !== value));
65
- } else {
66
- onChange([...values, value]);
67
- }
68
- };
69
-
70
- return (
71
- <div className={className}>
72
- <label className="block text-gray-700 text-sm font-bold mb-2">
73
- {label}
74
- </label>
75
- <div className="border rounded shadow max-h-60 overflow-y-auto">
76
- {options.length === 0 ? (
77
- <p className="text-gray-500 text-sm p-3">{noOptionsText}</p>
78
- ) : (
79
- <div className="space-y-1 p-1">
80
- {options.map(option => (
81
- <ToggleGroupItem
82
- key={option.value}
83
- value={option.value}
84
- isSelected={values.includes(option.value)}
85
- onClick={() => handleToggle(option.value)}
86
- >
87
- {option.label}
88
- </ToggleGroupItem>
89
- ))}
90
- </div>
91
- )}
92
- </div>
93
- {helpText && (
94
- <p className="text-xs text-gray-500 mt-1">
95
- {helpText}
96
- </p>
97
- )}
98
- </div>
99
- );
100
- };
101
-
102
- interface SwitchProps {
103
- checked: boolean;
104
- onCheckedChange: (checked: boolean) => void;
105
- disabled?: boolean;
106
- }
107
-
108
- export const Switch: React.FC<SwitchProps> = ({
109
- checked,
110
- onCheckedChange,
111
- disabled = false
112
- }) => {
113
- return (
114
- <button
115
- type="button"
116
- role="switch"
117
- aria-checked={checked}
118
- disabled={disabled}
119
- className={cn(
120
- "relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500",
121
- checked ? "bg-blue-600" : "bg-gray-200",
122
- disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
123
- )}
124
- onClick={() => !disabled && onCheckedChange(!checked)}
125
- >
126
- <span
127
- className={cn(
128
- "inline-block h-4 w-4 transform rounded-full bg-white transition-transform",
129
- checked ? "translate-x-6" : "translate-x-1"
130
- )}
131
- />
132
- </button>
133
- );
134
- };
@@ -1,38 +0,0 @@
1
- import { useState } from 'react'
2
- import { Tool } from '@/types'
3
- import { ChevronDown, ChevronRight } from '@/components/icons/LucideIcons'
4
-
5
- interface ToolCardProps {
6
- tool: Tool
7
- }
8
-
9
- const ToolCard = ({ tool }: ToolCardProps) => {
10
- const [isExpanded, setIsExpanded] = useState(false)
11
-
12
- return (
13
- <div className="bg-white shadow rounded-lg p-4 mb-4">
14
- <div
15
- className="flex justify-between items-center cursor-pointer"
16
- onClick={() => setIsExpanded(!isExpanded)}
17
- >
18
- <h3 className="text-lg font-medium text-gray-900">{tool.name}</h3>
19
- <button className="text-gray-400 hover:text-gray-600">
20
- {isExpanded ? <ChevronDown size={18} /> : <ChevronRight size={18} />}
21
- </button>
22
- </div>
23
- {isExpanded && (
24
- <div className="mt-4">
25
- <p className="text-gray-600 mb-2">{tool.description || 'No description available'}</p>
26
- <div className="bg-gray-50 rounded p-2">
27
- <h4 className="text-sm font-medium text-gray-900 mb-2">Input Schema:</h4>
28
- <pre className="text-xs text-gray-600 overflow-auto">
29
- {JSON.stringify(tool.inputSchema, null, 2)}
30
- </pre>
31
- </div>
32
- </div>
33
- )}
34
- </div>
35
- )
36
- }
37
-
38
- export default ToolCard