@nextclaw/ui 0.2.4 → 0.3.0
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.
- package/CHANGELOG.md +12 -0
- package/dist/assets/index-BIesvTqn.js +225 -0
- package/dist/assets/index-iSLahgqA.css +1 -0
- package/dist/index.html +2 -2
- package/dist/logos/aihubmix.png +0 -0
- package/dist/logos/anthropic.svg +1 -0
- package/dist/logos/dashscope.png +0 -0
- package/dist/logos/deepseek.png +0 -0
- package/dist/logos/dingtalk.svg +1 -0
- package/dist/logos/discord.svg +1 -0
- package/dist/logos/email.svg +1 -0
- package/dist/logos/feishu.svg +12 -0
- package/dist/logos/gemini.svg +1 -0
- package/dist/logos/groq.svg +1 -0
- package/dist/logos/minimax.svg +1 -0
- package/dist/logos/mochat.svg +6 -0
- package/dist/logos/moonshot.png +0 -0
- package/dist/logos/openai.svg +1 -0
- package/dist/logos/openrouter.svg +1 -0
- package/dist/logos/qq.svg +1 -0
- package/dist/logos/slack.svg +1 -0
- package/dist/logos/telegram.svg +1 -0
- package/dist/logos/vllm.svg +1 -0
- package/dist/logos/whatsapp.svg +1 -0
- package/dist/logos/zhipu.svg +15 -0
- package/package.json +1 -1
- package/public/logos/aihubmix.png +0 -0
- package/public/logos/anthropic.svg +1 -0
- package/public/logos/dashscope.png +0 -0
- package/public/logos/deepseek.png +0 -0
- package/public/logos/dingtalk.svg +1 -0
- package/public/logos/discord.svg +1 -0
- package/public/logos/email.svg +1 -0
- package/public/logos/feishu.svg +12 -0
- package/public/logos/gemini.svg +1 -0
- package/public/logos/groq.svg +1 -0
- package/public/logos/minimax.svg +1 -0
- package/public/logos/mochat.svg +6 -0
- package/public/logos/moonshot.png +0 -0
- package/public/logos/openai.svg +1 -0
- package/public/logos/openrouter.svg +1 -0
- package/public/logos/qq.svg +1 -0
- package/public/logos/slack.svg +1 -0
- package/public/logos/telegram.svg +1 -0
- package/public/logos/vllm.svg +1 -0
- package/public/logos/whatsapp.svg +1 -0
- package/public/logos/zhipu.svg +15 -0
- package/src/App.tsx +0 -3
- package/src/api/config.ts +0 -19
- package/src/api/types.ts +5 -8
- package/src/components/common/LogoBadge.tsx +35 -0
- package/src/components/common/StatusBadge.tsx +4 -4
- package/src/components/config/ChannelForm.tsx +16 -18
- package/src/components/config/ChannelsList.tsx +87 -37
- package/src/components/config/ModelConfig.tsx +25 -25
- package/src/components/config/ProviderForm.tsx +46 -11
- package/src/components/config/ProvidersList.tsx +90 -38
- package/src/components/layout/Header.tsx +7 -7
- package/src/components/layout/Sidebar.tsx +10 -23
- package/src/components/ui/HighlightCard.tsx +29 -29
- package/src/components/ui/button.tsx +13 -8
- package/src/components/ui/card.tsx +8 -7
- package/src/components/ui/dialog.tsx +8 -8
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/label.tsx +1 -1
- package/src/components/ui/switch.tsx +3 -3
- package/src/components/ui/tabs-custom.tsx +6 -6
- package/src/components/ui/tabs.tsx +7 -6
- package/src/hooks/useConfig.ts +2 -29
- package/src/index.css +103 -56
- package/src/lib/i18n.ts +7 -6
- package/src/lib/logos.ts +42 -0
- package/src/stores/ui.store.ts +1 -1
- package/src/styles/design-system.css +248 -0
- package/tailwind.config.js +118 -10
- package/dist/assets/index-C4OKhpdC.css +0 -1
- package/dist/assets/index-C8nOCIVG.js +0 -240
- package/src/components/config/UiConfig.tsx +0 -189
|
@@ -27,6 +27,7 @@ export function ProviderForm() {
|
|
|
27
27
|
const [apiKey, setApiKey] = useState('');
|
|
28
28
|
const [apiBase, setApiBase] = useState('');
|
|
29
29
|
const [extraHeaders, setExtraHeaders] = useState<Record<string, string> | null>(null);
|
|
30
|
+
const [wireApi, setWireApi] = useState<'auto' | 'chat' | 'responses'>('auto');
|
|
30
31
|
|
|
31
32
|
const providerName = providerModal.provider;
|
|
32
33
|
const providerSpec = meta?.providers.find((p) => p.name === providerName);
|
|
@@ -37,6 +38,9 @@ export function ProviderForm() {
|
|
|
37
38
|
setApiBase(providerConfig.apiBase || providerSpec?.defaultApiBase || '');
|
|
38
39
|
setExtraHeaders(providerConfig.extraHeaders || null);
|
|
39
40
|
setApiKey(''); // Always start with empty for security
|
|
41
|
+
const nextWireApi =
|
|
42
|
+
providerConfig.wireApi || providerSpec?.defaultWireApi || 'auto';
|
|
43
|
+
setWireApi(nextWireApi as 'auto' | 'chat' | 'responses');
|
|
40
44
|
}
|
|
41
45
|
}, [providerConfig, providerSpec]);
|
|
42
46
|
|
|
@@ -58,6 +62,14 @@ export function ProviderForm() {
|
|
|
58
62
|
payload.extraHeaders = extraHeaders;
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
if (providerSpec?.supportsWireApi) {
|
|
66
|
+
const currentWireApi =
|
|
67
|
+
providerConfig?.wireApi || providerSpec.defaultWireApi || 'auto';
|
|
68
|
+
if (wireApi !== currentWireApi) {
|
|
69
|
+
payload.wireApi = wireApi;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
if (!providerName) return;
|
|
62
74
|
|
|
63
75
|
updateProvider.mutate(
|
|
@@ -71,7 +83,7 @@ export function ProviderForm() {
|
|
|
71
83
|
<DialogContent className="sm:max-w-[500px]">
|
|
72
84
|
<DialogHeader>
|
|
73
85
|
<div className="flex items-center gap-3">
|
|
74
|
-
<div className="h-10 w-10 rounded-xl bg-
|
|
86
|
+
<div className="h-10 w-10 rounded-xl bg-primary flex items-center justify-center">
|
|
75
87
|
<KeyRound className="h-5 w-5 text-white" />
|
|
76
88
|
</div>
|
|
77
89
|
<div>
|
|
@@ -83,8 +95,8 @@ export function ProviderForm() {
|
|
|
83
95
|
|
|
84
96
|
<form onSubmit={handleSubmit} className="space-y-5 pt-2">
|
|
85
97
|
<div className="space-y-2.5">
|
|
86
|
-
<Label htmlFor="apiKey" className="text-sm font-medium text-
|
|
87
|
-
<KeyRound className="h-3.5 w-3.5 text-
|
|
98
|
+
<Label htmlFor="apiKey" className="text-sm font-medium text-gray-900 flex items-center gap-2">
|
|
99
|
+
<KeyRound className="h-3.5 w-3.5 text-gray-500" />
|
|
88
100
|
{t('apiKey')}
|
|
89
101
|
</Label>
|
|
90
102
|
<MaskedInput
|
|
@@ -93,13 +105,13 @@ export function ProviderForm() {
|
|
|
93
105
|
isSet={providerConfig?.apiKeySet}
|
|
94
106
|
onChange={(e) => setApiKey(e.target.value)}
|
|
95
107
|
placeholder={providerConfig?.apiKeySet ? t('apiKeySet') : 'Enter API Key'}
|
|
96
|
-
className="rounded-xl
|
|
108
|
+
className="rounded-xl"
|
|
97
109
|
/>
|
|
98
110
|
</div>
|
|
99
111
|
|
|
100
112
|
<div className="space-y-2.5">
|
|
101
|
-
<Label htmlFor="apiBase" className="text-sm font-medium text-
|
|
102
|
-
<Globe className="h-3.5 w-3.5 text-
|
|
113
|
+
<Label htmlFor="apiBase" className="text-sm font-medium text-gray-900 flex items-center gap-2">
|
|
114
|
+
<Globe className="h-3.5 w-3.5 text-gray-500" />
|
|
103
115
|
{t('apiBase')}
|
|
104
116
|
</Label>
|
|
105
117
|
<Input
|
|
@@ -108,13 +120,38 @@ export function ProviderForm() {
|
|
|
108
120
|
value={apiBase}
|
|
109
121
|
onChange={(e) => setApiBase(e.target.value)}
|
|
110
122
|
placeholder={providerSpec?.defaultApiBase || 'https://api.example.com'}
|
|
111
|
-
className="rounded-xl
|
|
123
|
+
className="rounded-xl"
|
|
112
124
|
/>
|
|
113
125
|
</div>
|
|
114
126
|
|
|
127
|
+
{providerSpec?.supportsWireApi && (
|
|
128
|
+
<div className="space-y-2.5">
|
|
129
|
+
<Label htmlFor="wireApi" className="text-sm font-medium text-gray-900 flex items-center gap-2">
|
|
130
|
+
<Hash className="h-3.5 w-3.5 text-gray-500" />
|
|
131
|
+
{t('wireApi')}
|
|
132
|
+
</Label>
|
|
133
|
+
<select
|
|
134
|
+
id="wireApi"
|
|
135
|
+
value={wireApi}
|
|
136
|
+
onChange={(e) => setWireApi(e.target.value as 'auto' | 'chat' | 'responses')}
|
|
137
|
+
className="flex h-10 w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
|
138
|
+
>
|
|
139
|
+
{(providerSpec.wireApiOptions || ['auto', 'chat', 'responses']).map((option) => (
|
|
140
|
+
<option key={option} value={option}>
|
|
141
|
+
{option === 'chat'
|
|
142
|
+
? t('wireApiChat')
|
|
143
|
+
: option === 'responses'
|
|
144
|
+
? t('wireApiResponses')
|
|
145
|
+
: t('wireApiAuto')}
|
|
146
|
+
</option>
|
|
147
|
+
))}
|
|
148
|
+
</select>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
|
|
115
152
|
<div className="space-y-2.5">
|
|
116
|
-
<Label className="text-sm font-medium text-
|
|
117
|
-
<Hash className="h-3.5 w-3.5 text-
|
|
153
|
+
<Label className="text-sm font-medium text-gray-900 flex items-center gap-2">
|
|
154
|
+
<Hash className="h-3.5 w-3.5 text-gray-500" />
|
|
118
155
|
{t('extraHeaders')}
|
|
119
156
|
</Label>
|
|
120
157
|
<KeyValueEditor
|
|
@@ -128,14 +165,12 @@ export function ProviderForm() {
|
|
|
128
165
|
type="button"
|
|
129
166
|
variant="outline"
|
|
130
167
|
onClick={closeProviderModal}
|
|
131
|
-
className="rounded-xl border-[hsl(40,20%,90%)] bg-white hover:bg-[hsl(40,20%,96%)]"
|
|
132
168
|
>
|
|
133
169
|
{t('cancel')}
|
|
134
170
|
</Button>
|
|
135
171
|
<Button
|
|
136
172
|
type="submit"
|
|
137
173
|
disabled={updateProvider.isPending}
|
|
138
|
-
className="rounded-xl bg-gradient-to-r from-orange-400 to-amber-500 hover:from-orange-500 hover:to-amber-600 text-white border-0"
|
|
139
174
|
>
|
|
140
175
|
{updateProvider.isPending ? 'Saving...' : t('save')}
|
|
141
176
|
</Button>
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { useConfig, useConfigMeta } from '@/hooks/useConfig';
|
|
2
2
|
import { Button } from '@/components/ui/button';
|
|
3
|
-
import {
|
|
4
|
-
import { KeyRound, Lock, Check, Plus, MoreHorizontal } from 'lucide-react';
|
|
3
|
+
import { KeyRound, Check, Settings } from 'lucide-react';
|
|
5
4
|
import { useState } from 'react';
|
|
6
5
|
import { ProviderForm } from './ProviderForm';
|
|
7
6
|
import { useUiStore } from '@/stores/ui.store';
|
|
8
7
|
import { cn } from '@/lib/utils';
|
|
9
8
|
import { Tabs } from '@/components/ui/tabs-custom';
|
|
10
|
-
import {
|
|
9
|
+
import { LogoBadge } from '@/components/common/LogoBadge';
|
|
10
|
+
import { getProviderLogo } from '@/lib/logos';
|
|
11
11
|
|
|
12
12
|
export function ProvidersList() {
|
|
13
13
|
const { data: config } = useConfig();
|
|
14
14
|
const { data: meta } = useConfigMeta();
|
|
15
15
|
const { openProviderModal } = useUiStore();
|
|
16
|
-
const [activeTab, setActiveTab] = useState('
|
|
16
|
+
const [activeTab, setActiveTab] = useState('installed');
|
|
17
17
|
|
|
18
18
|
if (!config || !meta) {
|
|
19
|
-
return <div className="p-8">Loading...</div>;
|
|
19
|
+
return <div className="p-8">Loading...</div>;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const tabs = [
|
|
@@ -24,66 +24,118 @@ export function ProvidersList() {
|
|
|
24
24
|
{ id: 'all', label: 'All Providers' }
|
|
25
25
|
];
|
|
26
26
|
|
|
27
|
+
const filteredProviders = activeTab === 'installed'
|
|
28
|
+
? meta.providers.filter((p) => config.providers[p.name]?.apiKeySet)
|
|
29
|
+
: meta.providers;
|
|
30
|
+
|
|
27
31
|
return (
|
|
28
32
|
<div className="animate-fade-in pb-20">
|
|
29
33
|
<div className="flex items-center justify-between mb-8">
|
|
30
|
-
<h2 className="text-2xl font-bold text-
|
|
34
|
+
<h2 className="text-2xl font-bold text-gray-900">AI Providers</h2>
|
|
31
35
|
</div>
|
|
32
36
|
|
|
33
|
-
{
|
|
34
|
-
<Tabs tabs={tabs} activeTab={activeTab === 'featured' ? 'all' : activeTab} onChange={setActiveTab} />
|
|
37
|
+
<Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} />
|
|
35
38
|
|
|
36
|
-
{/* Provider
|
|
37
|
-
<div className="
|
|
38
|
-
{(
|
|
39
|
-
? meta.providers.filter((p) => config.providers[p.name]?.apiKeySet)
|
|
40
|
-
: meta.providers
|
|
41
|
-
).map((provider) => {
|
|
39
|
+
{/* Provider Cards Grid */}
|
|
40
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
|
41
|
+
{filteredProviders.map((provider) => {
|
|
42
42
|
const providerConfig = config.providers[provider.name];
|
|
43
43
|
const hasConfig = providerConfig?.apiKeySet;
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
46
|
<div
|
|
47
47
|
key={provider.name}
|
|
48
|
-
className=
|
|
48
|
+
className={cn(
|
|
49
|
+
'group relative flex flex-col p-5 rounded-2xl border transition-all duration-base cursor-pointer',
|
|
50
|
+
'hover:shadow-card-hover hover:-translate-y-0.5',
|
|
51
|
+
hasConfig
|
|
52
|
+
? 'bg-white border-gray-200 hover:border-gray-300'
|
|
53
|
+
: 'bg-gray-50 border-gray-200 hover:border-gray-300 hover:bg-white'
|
|
54
|
+
)}
|
|
49
55
|
onClick={() => openProviderModal(provider.name)}
|
|
50
56
|
>
|
|
51
|
-
{/* Logo
|
|
52
|
-
<div className="
|
|
53
|
-
<
|
|
57
|
+
{/* Header with Logo and Status */}
|
|
58
|
+
<div className="flex items-start justify-between mb-4">
|
|
59
|
+
<LogoBadge
|
|
60
|
+
name={provider.name}
|
|
61
|
+
src={getProviderLogo(provider.name)}
|
|
62
|
+
className={cn(
|
|
63
|
+
'h-12 w-12 rounded-xl border transition-all',
|
|
64
|
+
hasConfig
|
|
65
|
+
? 'bg-white border-primary'
|
|
66
|
+
: 'bg-white border-gray-200 group-hover:border-gray-300'
|
|
67
|
+
)}
|
|
68
|
+
imgClassName="h-7 w-7"
|
|
69
|
+
fallback={(
|
|
70
|
+
<span className={cn(
|
|
71
|
+
'text-lg font-bold uppercase',
|
|
72
|
+
hasConfig ? 'text-gray-900' : 'text-gray-500'
|
|
73
|
+
)}>
|
|
74
|
+
{provider.name[0]}
|
|
75
|
+
</span>
|
|
76
|
+
)}
|
|
77
|
+
/>
|
|
78
|
+
|
|
79
|
+
{/* Status Badge */}
|
|
80
|
+
{hasConfig ? (
|
|
81
|
+
<div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-50 text-emerald-600">
|
|
82
|
+
<Check className="h-3.5 w-3.5" />
|
|
83
|
+
<span className="text-[11px] font-bold">Ready</span>
|
|
84
|
+
</div>
|
|
85
|
+
) : (
|
|
86
|
+
<div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-gray-100 text-gray-500">
|
|
87
|
+
<Settings className="h-3.5 w-3.5" />
|
|
88
|
+
<span className="text-[11px] font-bold">Setup</span>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
54
91
|
</div>
|
|
55
92
|
|
|
56
|
-
{/* Info */}
|
|
57
|
-
<div className="flex-1
|
|
58
|
-
<h3 className="text-[
|
|
93
|
+
{/* Provider Info */}
|
|
94
|
+
<div className="flex-1">
|
|
95
|
+
<h3 className="text-[15px] font-bold text-gray-900 mb-1">
|
|
59
96
|
{provider.displayName || provider.name}
|
|
60
97
|
</h3>
|
|
61
|
-
<p className="text-[12px] text-
|
|
62
|
-
{provider.name === 'openai'
|
|
98
|
+
<p className="text-[12px] text-gray-500 leading-relaxed line-clamp-2">
|
|
99
|
+
{provider.name === 'openai'
|
|
100
|
+
? 'Leading AI models including GPT-4 and GPT-3.5'
|
|
101
|
+
: 'Configure AI services for your agents'}
|
|
63
102
|
</p>
|
|
64
103
|
</div>
|
|
65
104
|
|
|
66
|
-
{/*
|
|
67
|
-
<div className="
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
<MoreHorizontal className="h-4 w-4" />
|
|
80
|
-
</button>
|
|
105
|
+
{/* Footer with Action */}
|
|
106
|
+
<div className="mt-4 pt-4 border-t border-gray-100">
|
|
107
|
+
<Button
|
|
108
|
+
variant={hasConfig ? 'ghost' : 'default'}
|
|
109
|
+
size="sm"
|
|
110
|
+
className="w-full rounded-xl text-xs font-semibold h-9"
|
|
111
|
+
onClick={(e) => {
|
|
112
|
+
e.stopPropagation();
|
|
113
|
+
openProviderModal(provider.name);
|
|
114
|
+
}}
|
|
115
|
+
>
|
|
116
|
+
{hasConfig ? 'Configure' : 'Add Provider'}
|
|
117
|
+
</Button>
|
|
81
118
|
</div>
|
|
82
119
|
</div>
|
|
83
120
|
);
|
|
84
121
|
})}
|
|
85
122
|
</div>
|
|
86
123
|
|
|
124
|
+
{/* Empty State */}
|
|
125
|
+
{filteredProviders.length === 0 && (
|
|
126
|
+
<div className="flex flex-col items-center justify-center py-16 text-center">
|
|
127
|
+
<div className="h-16 w-16 flex items-center justify-center rounded-2xl bg-gray-100 mb-4">
|
|
128
|
+
<KeyRound className="h-8 w-8 text-gray-400" />
|
|
129
|
+
</div>
|
|
130
|
+
<h3 className="text-[15px] font-bold text-gray-900 mb-2">
|
|
131
|
+
No providers configured
|
|
132
|
+
</h3>
|
|
133
|
+
<p className="text-[13px] text-gray-500 max-w-sm">
|
|
134
|
+
Add an AI provider to start using the platform. Click on any provider to configure it.
|
|
135
|
+
</p>
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
|
|
87
139
|
<ProviderForm />
|
|
88
140
|
</div>
|
|
89
141
|
);
|
|
@@ -7,27 +7,27 @@ interface HeaderProps {
|
|
|
7
7
|
|
|
8
8
|
export function Header({ title, description }: HeaderProps) {
|
|
9
9
|
return (
|
|
10
|
-
<header className="h-16 bg-white/80 backdrop-blur-md sticky top-0 z-10 border-b border-
|
|
10
|
+
<header className="h-16 bg-white/80 backdrop-blur-md sticky top-0 z-10 border-b border-gray-200 flex items-center justify-between px-6 transition-all duration-base">
|
|
11
11
|
<div className="flex items-center gap-4">
|
|
12
12
|
{title && (
|
|
13
13
|
<div>
|
|
14
|
-
<h2 className="text-base font-semibold text-
|
|
14
|
+
<h2 className="text-base font-semibold text-gray-900">{title}</h2>
|
|
15
15
|
{description && (
|
|
16
|
-
<p className="text-xs text-
|
|
16
|
+
<p className="text-xs text-gray-500">{description}</p>
|
|
17
17
|
)}
|
|
18
18
|
</div>
|
|
19
19
|
)}
|
|
20
20
|
</div>
|
|
21
21
|
|
|
22
22
|
<div className="flex items-center gap-3">
|
|
23
|
-
<button className="h-9 w-9 rounded-lg bg-
|
|
23
|
+
<button className="h-9 w-9 rounded-lg bg-gray-100 flex items-center justify-center text-gray-500 hover:bg-gray-200 hover:text-gray-700 transition-colors duration-fast">
|
|
24
24
|
<Search className="h-4 w-4" />
|
|
25
25
|
</button>
|
|
26
|
-
<button className="h-9 w-9 rounded-lg bg-
|
|
26
|
+
<button className="h-9 w-9 rounded-lg bg-gray-100 flex items-center justify-center text-gray-500 hover:bg-gray-200 hover:text-gray-700 transition-colors duration-fast relative">
|
|
27
27
|
<Bell className="h-4 w-4" />
|
|
28
|
-
<span className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-
|
|
28
|
+
<span className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-primary" />
|
|
29
29
|
</button>
|
|
30
|
-
<div className="h-9 w-9 rounded-lg bg-gradient-to-br from-
|
|
30
|
+
<div className="h-9 w-9 rounded-lg bg-gradient-to-br from-primary to-primary-600 flex items-center justify-center">
|
|
31
31
|
<span className="text-xs font-semibold text-white">N</span>
|
|
32
32
|
</div>
|
|
33
33
|
</div>
|
|
@@ -1,37 +1,22 @@
|
|
|
1
1
|
import { useUiStore } from '@/stores/ui.store';
|
|
2
2
|
import { cn } from '@/lib/utils';
|
|
3
|
-
import {
|
|
4
|
-
Cpu,
|
|
5
|
-
MessageSquare,
|
|
6
|
-
Sparkles,
|
|
7
|
-
ChevronRight,
|
|
8
|
-
Settings
|
|
9
|
-
} from 'lucide-react';
|
|
3
|
+
import { Cpu, MessageSquare, Sparkles } from 'lucide-react';
|
|
10
4
|
|
|
11
5
|
const navItems = [
|
|
12
6
|
{
|
|
13
7
|
id: 'model' as const,
|
|
14
8
|
label: 'Models',
|
|
15
9
|
icon: Cpu,
|
|
16
|
-
color: 'text-[hsl(30,15%,10%)]'
|
|
17
10
|
},
|
|
18
11
|
{
|
|
19
12
|
id: 'providers' as const,
|
|
20
13
|
label: 'Providers',
|
|
21
14
|
icon: Sparkles,
|
|
22
|
-
color: 'text-[hsl(30,15%,10%)]'
|
|
23
15
|
},
|
|
24
16
|
{
|
|
25
17
|
id: 'channels' as const,
|
|
26
18
|
label: 'Channels',
|
|
27
19
|
icon: MessageSquare,
|
|
28
|
-
color: 'text-[hsl(30,15%,10%)]'
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
id: 'ui' as const,
|
|
32
|
-
label: 'Appearance',
|
|
33
|
-
icon: Settings,
|
|
34
|
-
color: 'text-[hsl(30,15%,10%)]'
|
|
35
20
|
}
|
|
36
21
|
];
|
|
37
22
|
|
|
@@ -43,10 +28,10 @@ export function Sidebar() {
|
|
|
43
28
|
{/* Logo Area */}
|
|
44
29
|
<div className="px-3 mb-8">
|
|
45
30
|
<div className="flex items-center gap-2 group cursor-pointer">
|
|
46
|
-
<div className="h-7 w-7 rounded-lg overflow-hidden flex items-center justify-center transition-transform group-hover:scale-110">
|
|
31
|
+
<div className="h-7 w-7 rounded-lg overflow-hidden flex items-center justify-center transition-transform duration-fast group-hover:scale-110">
|
|
47
32
|
<img src="/logo.svg" alt="NextClaw Logo" className="h-full w-full object-contain" />
|
|
48
33
|
</div>
|
|
49
|
-
<h1 className="text-lg font-bold text-
|
|
34
|
+
<h1 className="text-lg font-bold text-gray-900 tracking-tight">nextclaw</h1>
|
|
50
35
|
</div>
|
|
51
36
|
</div>
|
|
52
37
|
|
|
@@ -62,20 +47,22 @@ export function Sidebar() {
|
|
|
62
47
|
<button
|
|
63
48
|
onClick={() => setActiveTab(item.id)}
|
|
64
49
|
className={cn(
|
|
65
|
-
'group w-full flex items-center gap-3 px-3 py-2 rounded-lg text-
|
|
50
|
+
'group w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-fast',
|
|
66
51
|
isActive
|
|
67
|
-
? 'bg-
|
|
68
|
-
: 'text-
|
|
52
|
+
? 'bg-primary-100 text-primary-700'
|
|
53
|
+
: 'text-gray-600 hover:bg-gray-100 hover:text-gray-900'
|
|
69
54
|
)}
|
|
70
55
|
>
|
|
71
|
-
<Icon className={cn(
|
|
56
|
+
<Icon className={cn(
|
|
57
|
+
'h-4 w-4 transition-transform duration-fast group-hover:scale-110',
|
|
58
|
+
isActive ? 'text-primary' : 'text-gray-500'
|
|
59
|
+
)} />
|
|
72
60
|
<span className="flex-1 text-left">{item.label}</span>
|
|
73
61
|
</button>
|
|
74
62
|
</li>
|
|
75
63
|
);
|
|
76
64
|
})}
|
|
77
65
|
</ul>
|
|
78
|
-
|
|
79
66
|
</nav>
|
|
80
67
|
</aside>
|
|
81
68
|
);
|
|
@@ -3,38 +3,38 @@ import { cn } from '@/lib/utils';
|
|
|
3
3
|
import { ArrowRight } from 'lucide-react';
|
|
4
4
|
|
|
5
5
|
interface HighlightCardProps {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
category: string;
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
image: string;
|
|
10
|
+
gradient: string;
|
|
11
|
+
className?: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function HighlightCard({ category, title, description, image, gradient, className }: HighlightCardProps) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
return (
|
|
16
|
+
<div className={cn(
|
|
17
|
+
'group relative overflow-hidden rounded-xl bg-white border border-gray-200 flex h-[180px] transition-all duration-base hover:shadow-card-hover hover:border-gray-300 cursor-pointer',
|
|
18
|
+
className
|
|
19
|
+
)}>
|
|
20
|
+
<div className="flex-1 p-6 flex flex-col">
|
|
21
|
+
<span className="text-[10px] font-semibold text-gray-400 uppercase tracking-widest mb-2">{category}</span>
|
|
22
|
+
<h3 className="text-lg font-semibold text-gray-900 leading-tight mb-2 group-hover:text-primary transition-colors duration-fast">{title}</h3>
|
|
23
|
+
<p className="text-sm text-gray-500 line-clamp-2 leading-relaxed max-w-[200px]">{description}</p>
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
|
|
30
|
-
<div className={cn('w-[160px] relative overflow-hidden', gradient)}>
|
|
31
|
-
<img
|
|
32
|
-
src={image}
|
|
33
|
-
alt={title}
|
|
34
|
-
className="w-full h-full object-cover mix-blend-multiply opacity-90 group-hover:scale-110 transition-transform duration-500"
|
|
35
|
-
/>
|
|
36
|
-
<div className="absolute inset-0 bg-gradient-to-l from-transparent to-white/10" />
|
|
37
|
-
</div>
|
|
25
|
+
<div className="mt-auto flex items-center gap-1 text-xs font-medium text-primary opacity-0 group-hover:opacity-100 transition-opacity duration-fast">
|
|
26
|
+
Learn more <ArrowRight className="h-3 w-3" />
|
|
38
27
|
</div>
|
|
39
|
-
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div className={cn('w-[160px] relative overflow-hidden', gradient)}>
|
|
31
|
+
<img
|
|
32
|
+
src={image}
|
|
33
|
+
alt={title}
|
|
34
|
+
className="w-full h-full object-cover mix-blend-multiply opacity-90 group-hover:scale-110 transition-transform duration-slow"
|
|
35
|
+
/>
|
|
36
|
+
<div className="absolute inset-0 bg-gradient-to-l from-transparent to-white/10" />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
40
|
}
|
|
@@ -3,21 +3,26 @@ import { cva, type VariantProps } from 'class-variance-authority';
|
|
|
3
3
|
import { cn } from '@/lib/utils';
|
|
4
4
|
|
|
5
5
|
const buttonVariants = cva(
|
|
6
|
-
'inline-flex items-center justify-center whitespace-nowrap rounded-
|
|
6
|
+
'inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium ring-offset-background transition-all duration-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
7
7
|
{
|
|
8
8
|
variants: {
|
|
9
9
|
variant: {
|
|
10
|
-
default: 'bg-primary text-primary-foreground hover:bg-primary
|
|
10
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm',
|
|
11
11
|
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
12
|
-
outline: 'border border-input bg-background hover:bg-
|
|
13
|
-
secondary: 'bg-secondary text-secondary-foreground hover:bg-
|
|
14
|
-
ghost: 'hover:bg-
|
|
15
|
-
link: 'text-primary underline-offset-4 hover:underline'
|
|
12
|
+
outline: 'border border-input bg-background hover:bg-secondary hover:text-secondary-foreground',
|
|
13
|
+
secondary: 'bg-secondary text-secondary-foreground hover:bg-gray-200',
|
|
14
|
+
ghost: 'hover:bg-secondary hover:text-secondary-foreground',
|
|
15
|
+
link: 'text-primary underline-offset-4 hover:underline',
|
|
16
|
+
// New variants matching the design
|
|
17
|
+
primary: 'bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm',
|
|
18
|
+
subtle: 'bg-secondary text-secondary-foreground hover:bg-gray-200',
|
|
19
|
+
'primary-outline': 'border-2 border-primary text-primary hover:bg-primary hover:text-primary-foreground'
|
|
16
20
|
},
|
|
17
21
|
size: {
|
|
18
22
|
default: 'h-10 px-4 py-2',
|
|
19
|
-
sm: 'h-
|
|
20
|
-
lg: 'h-
|
|
23
|
+
sm: 'h-8 rounded-md px-3 text-xs',
|
|
24
|
+
lg: 'h-12 rounded-xl px-6 text-base',
|
|
25
|
+
xl: 'h-14 rounded-xl px-8 text-base',
|
|
21
26
|
icon: 'h-10 w-10'
|
|
22
27
|
}
|
|
23
28
|
},
|
|
@@ -3,12 +3,13 @@ import { cn } from '@/lib/utils';
|
|
|
3
3
|
|
|
4
4
|
const Card = React.forwardRef<
|
|
5
5
|
HTMLDivElement,
|
|
6
|
-
React.HTMLAttributes<HTMLDivElement>
|
|
7
|
-
>(({ className, ...props }, ref) => (
|
|
6
|
+
React.HTMLAttributes<HTMLDivElement> & { hover?: boolean }
|
|
7
|
+
>(({ className, hover = true, ...props }, ref) => (
|
|
8
8
|
<div
|
|
9
9
|
ref={ref}
|
|
10
10
|
className={cn(
|
|
11
|
-
'rounded-
|
|
11
|
+
'rounded-xl border border-gray-200 bg-white text-card-foreground shadow-card transition-all duration-base',
|
|
12
|
+
hover && 'hover:shadow-card-hover hover:border-gray-300',
|
|
12
13
|
className
|
|
13
14
|
)}
|
|
14
15
|
{...props}
|
|
@@ -22,7 +23,7 @@ const CardHeader = React.forwardRef<
|
|
|
22
23
|
>(({ className, ...props }, ref) => (
|
|
23
24
|
<div
|
|
24
25
|
ref={ref}
|
|
25
|
-
className={cn('flex flex-col space-y-
|
|
26
|
+
className={cn('flex flex-col space-y-2 p-6', className)}
|
|
26
27
|
{...props}
|
|
27
28
|
/>
|
|
28
29
|
));
|
|
@@ -35,7 +36,7 @@ const CardTitle = React.forwardRef<
|
|
|
35
36
|
<h3
|
|
36
37
|
ref={ref}
|
|
37
38
|
className={cn(
|
|
38
|
-
'text-
|
|
39
|
+
'text-lg font-semibold leading-tight tracking-tight text-gray-900',
|
|
39
40
|
className
|
|
40
41
|
)}
|
|
41
42
|
{...props}
|
|
@@ -49,7 +50,7 @@ const CardDescription = React.forwardRef<
|
|
|
49
50
|
>(({ className, ...props }, ref) => (
|
|
50
51
|
<p
|
|
51
52
|
ref={ref}
|
|
52
|
-
className={cn('text-sm text-
|
|
53
|
+
className={cn('text-sm text-gray-500 leading-relaxed', className)}
|
|
53
54
|
{...props}
|
|
54
55
|
/>
|
|
55
56
|
));
|
|
@@ -69,7 +70,7 @@ const CardFooter = React.forwardRef<
|
|
|
69
70
|
>(({ className, ...props }, ref) => (
|
|
70
71
|
<div
|
|
71
72
|
ref={ref}
|
|
72
|
-
className={cn('flex items-center p-6 pt-0', className)}
|
|
73
|
+
className={cn('flex items-center justify-between p-6 pt-0', className)}
|
|
73
74
|
{...props}
|
|
74
75
|
/>
|
|
75
76
|
));
|
|
@@ -19,7 +19,7 @@ const DialogOverlay = React.forwardRef<
|
|
|
19
19
|
<DialogPrimitive.Overlay
|
|
20
20
|
ref={ref}
|
|
21
21
|
className={cn(
|
|
22
|
-
"fixed inset-0 z-50 bg-black/
|
|
22
|
+
"fixed inset-0 z-50 bg-black/40 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
23
23
|
className
|
|
24
24
|
)}
|
|
25
25
|
{...props}
|
|
@@ -36,14 +36,14 @@ const DialogContent = React.forwardRef<
|
|
|
36
36
|
<DialogPrimitive.Content
|
|
37
37
|
ref={ref}
|
|
38
38
|
className={cn(
|
|
39
|
-
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-
|
|
39
|
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-gray-200 bg-white p-6 shadow-xl duration-base 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%] rounded-xl",
|
|
40
40
|
className
|
|
41
41
|
)}
|
|
42
42
|
{...props}
|
|
43
43
|
>
|
|
44
44
|
{children}
|
|
45
|
-
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-lg opacity-70 ring-offset-white transition-
|
|
46
|
-
<X className="h-4 w-4 text-
|
|
45
|
+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-lg p-1 opacity-70 ring-offset-white transition-all duration-fast hover:opacity-100 hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:pointer-events-none">
|
|
46
|
+
<X className="h-4 w-4 text-gray-500" />
|
|
47
47
|
<span className="sr-only">Close</span>
|
|
48
48
|
</DialogPrimitive.Close>
|
|
49
49
|
</DialogPrimitive.Content>
|
|
@@ -57,7 +57,7 @@ const DialogHeader = ({
|
|
|
57
57
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
58
58
|
<div
|
|
59
59
|
className={cn(
|
|
60
|
-
"flex flex-col space-y-
|
|
60
|
+
"flex flex-col space-y-2 text-center sm:text-left",
|
|
61
61
|
className
|
|
62
62
|
)}
|
|
63
63
|
{...props}
|
|
@@ -71,7 +71,7 @@ const DialogFooter = ({
|
|
|
71
71
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
72
72
|
<div
|
|
73
73
|
className={cn(
|
|
74
|
-
"flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-
|
|
74
|
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-3",
|
|
75
75
|
className
|
|
76
76
|
)}
|
|
77
77
|
{...props}
|
|
@@ -86,7 +86,7 @@ const DialogTitle = React.forwardRef<
|
|
|
86
86
|
<DialogPrimitive.Title
|
|
87
87
|
ref={ref}
|
|
88
88
|
className={cn(
|
|
89
|
-
"text-lg font-semibold leading-
|
|
89
|
+
"text-lg font-semibold leading-tight tracking-tight text-gray-900",
|
|
90
90
|
className
|
|
91
91
|
)}
|
|
92
92
|
{...props}
|
|
@@ -100,7 +100,7 @@ const DialogDescription = React.forwardRef<
|
|
|
100
100
|
>(({ className, ...props }, ref) => (
|
|
101
101
|
<DialogPrimitive.Description
|
|
102
102
|
ref={ref}
|
|
103
|
-
className={cn("text-sm text-
|
|
103
|
+
className={cn("text-sm text-gray-500 leading-relaxed", className)}
|
|
104
104
|
{...props}
|
|
105
105
|
/>
|
|
106
106
|
))
|