@nextclaw/ui 0.5.6 → 0.5.8

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 (32) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/assets/index-BtwwwWcv.css +1 -0
  3. package/dist/assets/index-STUSj6p9.js +337 -0
  4. package/dist/index.html +2 -2
  5. package/package.json +1 -1
  6. package/src/components/common/StatusBadge.tsx +9 -13
  7. package/src/components/config/ChannelForm.tsx +10 -0
  8. package/src/components/config/ChannelsList.tsx +33 -59
  9. package/src/components/config/CronConfig.tsx +28 -30
  10. package/src/components/config/ModelConfig.tsx +1 -1
  11. package/src/components/config/ProvidersList.tsx +32 -58
  12. package/src/components/config/RuntimeConfig.tsx +1 -1
  13. package/src/components/config/SessionsConfig.tsx +1 -1
  14. package/src/components/layout/AppLayout.tsx +3 -3
  15. package/src/components/layout/Header.tsx +4 -19
  16. package/src/components/layout/Sidebar.tsx +13 -21
  17. package/src/components/marketplace/MarketplacePage.tsx +17 -17
  18. package/src/components/ui/action-link.tsx +27 -0
  19. package/src/components/ui/button.tsx +12 -13
  20. package/src/components/ui/card.tsx +5 -5
  21. package/src/components/ui/config-card.tsx +71 -0
  22. package/src/components/ui/dialog.tsx +1 -1
  23. package/src/components/ui/input.tsx +2 -2
  24. package/src/components/ui/select.tsx +1 -1
  25. package/src/components/ui/status-dot.tsx +51 -0
  26. package/src/components/ui/switch.tsx +2 -2
  27. package/src/components/ui/tabs-custom.tsx +9 -6
  28. package/src/components/ui/tabs.tsx +3 -3
  29. package/src/index.css +25 -37
  30. package/src/styles/design-system.css +53 -49
  31. package/dist/assets/index-B3foa-xK.css +0 -1
  32. package/dist/assets/index-CsMwBztg.js +0 -347
package/dist/index.html CHANGED
@@ -6,8 +6,8 @@
6
6
  <link rel="icon" type="image/svg+xml" href="/logo.svg" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
8
  <title>NextClaw - 系统配置</title>
9
- <script type="module" crossorigin src="/assets/index-CsMwBztg.js"></script>
10
- <link rel="stylesheet" crossorigin href="/assets/index-B3foa-xK.css">
9
+ <script type="module" crossorigin src="/assets/index-STUSj6p9.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-BtwwwWcv.css">
11
11
  </head>
12
12
 
13
13
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.5.6",
3
+ "version": "0.5.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,5 +1,5 @@
1
1
  import { cn } from '@/lib/utils';
2
- import { Check, X, Loader2 } from 'lucide-react';
2
+ import { Loader2 } from 'lucide-react';
3
3
  import { t } from '@/lib/i18n';
4
4
 
5
5
  type Status = 'connected' | 'disconnected' | 'connecting';
@@ -11,45 +11,41 @@ interface StatusBadgeProps {
11
11
 
12
12
  const statusConfig: Record<
13
13
  Status,
14
- { label: string; dotClass: string; textClass: string; bgClass: string; icon: typeof Check }
14
+ { label: string; dotClass: string; textClass: string; bgClass: string }
15
15
  > = {
16
16
  connected: {
17
17
  label: t('connected'),
18
18
  dotClass: 'bg-emerald-500',
19
19
  textClass: 'text-emerald-600',
20
20
  bgClass: 'bg-emerald-50',
21
- icon: Check
22
21
  },
23
22
  disconnected: {
24
23
  label: t('disconnected'),
25
- dotClass: 'bg-gray-400',
26
- textClass: 'text-gray-500',
27
- bgClass: 'bg-gray-100',
28
- icon: X
24
+ dotClass: 'bg-gray-300',
25
+ textClass: 'text-gray-400',
26
+ bgClass: 'bg-gray-100/80',
29
27
  },
30
28
  connecting: {
31
29
  label: t('connecting'),
32
30
  dotClass: 'bg-amber-400',
33
31
  textClass: 'text-amber-600',
34
32
  bgClass: 'bg-amber-50',
35
- icon: Loader2
36
33
  }
37
34
  };
38
35
 
39
36
  export function StatusBadge({ status, className }: StatusBadgeProps) {
40
37
  const config = statusConfig[status];
41
- const Icon = config.icon;
42
38
 
43
39
  return (
44
40
  <div className={cn(
45
- 'flex items-center gap-2 px-3 py-1.5 rounded-full',
41
+ 'flex items-center gap-1.5 px-2 py-0.5 rounded-full',
46
42
  config.bgClass,
47
43
  className
48
44
  )}>
49
- <div className={cn('h-2 w-2 rounded-full', config.dotClass)} />
50
- <span className={cn('text-xs font-medium flex items-center gap-1', config.textClass)}>
45
+ <span className={cn('h-1.5 w-1.5 rounded-full', config.dotClass)} />
46
+ <span className={cn('text-[11px] font-medium flex items-center gap-1', config.textClass)}>
51
47
  {config.label}
52
- {status === 'connecting' && <Icon className="h-3 w-3 animate-spin" />}
48
+ {status === 'connecting' && <Loader2 className="h-2.5 w-2.5 animate-spin" />}
53
49
  </span>
54
50
  </div>
55
51
  );
@@ -38,6 +38,13 @@ const GROUP_POLICY_OPTIONS: ChannelOption[] = [
38
38
  { value: 'disabled', label: 'disabled' }
39
39
  ];
40
40
 
41
+ const STREAMING_MODE_OPTIONS: ChannelOption[] = [
42
+ { value: 'off', label: 'off' },
43
+ { value: 'partial', label: 'partial' },
44
+ { value: 'block', label: 'block' },
45
+ { value: 'progress', label: 'progress' }
46
+ ];
47
+
41
48
  // Field icon mapping
42
49
  const getFieldIcon = (fieldName: string) => {
43
50
  if (fieldName.includes('token') || fieldName.includes('secret') || fieldName.includes('password')) {
@@ -82,6 +89,9 @@ const CHANNEL_FIELDS: Record<string, ChannelField[]> = {
82
89
  { name: 'intents', type: 'number', label: t('intents') },
83
90
  { name: 'proxy', type: 'text', label: t('proxy') },
84
91
  { name: 'mediaMaxMb', type: 'number', label: 'Attachment Max Size (MB)' },
92
+ { name: 'streaming', type: 'select', label: 'Streaming Mode', options: STREAMING_MODE_OPTIONS },
93
+ { name: 'draftChunk', type: 'json', label: 'Draft Chunking (JSON)' },
94
+ { name: 'textChunkLimit', type: 'number', label: 'Text Chunk Limit' },
85
95
  { name: 'accountId', type: 'text', label: 'Account ID' },
86
96
  { name: 'dmPolicy', type: 'select', label: 'DM Policy', options: DM_POLICY_OPTIONS },
87
97
  { name: 'groupPolicy', type: 'select', label: 'Group Policy', options: GROUP_POLICY_OPTIONS },
@@ -1,13 +1,16 @@
1
1
  import { useConfig, useConfigMeta, useConfigSchema } from '@/hooks/useConfig';
2
- import { MessageCircle, Mail, MessageSquare, Slack, ExternalLink, Bell, Zap, Radio, ArrowRight } from 'lucide-react';
2
+ import { MessageCircle, Mail, MessageSquare, Slack, ExternalLink, Bell, ArrowRight } from 'lucide-react';
3
3
  import { useState } from 'react';
4
4
  import { ChannelForm } from './ChannelForm';
5
5
  import { useUiStore } from '@/stores/ui.store';
6
- import { cn } from '@/lib/utils';
7
6
  import { Tabs } from '@/components/ui/tabs-custom';
8
7
  import { LogoBadge } from '@/components/common/LogoBadge';
9
8
  import { getChannelLogo } from '@/lib/logos';
10
9
  import { hintForPath } from '@/lib/config-hints';
10
+ import { ConfigCard, ConfigCardHeader, ConfigCardBody, ConfigCardFooter } from '@/components/ui/config-card';
11
+ import { StatusDot } from '@/components/ui/status-dot';
12
+ import { ActionLink } from '@/components/ui/action-link';
13
+ import { cn } from '@/lib/utils';
11
14
 
12
15
  const channelIcons: Record<string, typeof MessageCircle> = {
13
16
  telegram: MessageCircle,
@@ -50,8 +53,8 @@ export function ChannelsList() {
50
53
 
51
54
  return (
52
55
  <div className="animate-fade-in pb-20">
53
- <div className="flex items-center justify-between mb-8">
54
- <h2 className="text-2xl font-bold text-gray-900">Message Channels</h2>
56
+ <div className="flex items-center justify-between mb-6">
57
+ <h2 className="text-xl font-semibold text-gray-900">Message Channels</h2>
55
58
  </div>
56
59
 
57
60
  <Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} />
@@ -69,76 +72,47 @@ export function ChannelsList() {
69
72
  'Configure this communication channel';
70
73
 
71
74
  return (
72
- <div
73
- key={channel.name}
74
- className={cn(
75
- 'group relative flex flex-col p-5 rounded-2xl border transition-all duration-base cursor-pointer',
76
- 'hover:shadow-card-hover hover:-translate-y-0.5',
77
- enabled
78
- ? 'bg-white border-gray-200 shadow-sm'
79
- : 'bg-white border-transparent shadow-sm hover:border-gray-200'
80
- )}
81
- onClick={() => openChannelModal(channel.name)}
82
- >
83
- {/* Header with Icon and Status */}
84
- <div className="flex items-start justify-between mb-4">
75
+ <ConfigCard key={channel.name} onClick={() => openChannelModal(channel.name)}>
76
+ <ConfigCardHeader>
85
77
  <LogoBadge
86
78
  name={channel.name}
87
79
  src={getChannelLogo(channel.name)}
88
80
  className={cn(
89
- 'h-12 w-12 rounded-xl border transition-all',
81
+ 'h-11 w-11 rounded-xl border transition-all',
90
82
  enabled
91
- ? 'bg-white border-primary'
92
- : 'bg-white border-gray-200 group-hover:border-gray-300'
83
+ ? 'bg-white border-primary/30'
84
+ : 'bg-white border-gray-200/60 group-hover:border-gray-300'
93
85
  )}
94
- imgClassName="h-6 w-6"
95
- fallback={<Icon className="h-6 w-6" />}
86
+ imgClassName="h-5 w-5"
87
+ fallback={<Icon className="h-5 w-5" />}
96
88
  />
89
+ <StatusDot
90
+ status={enabled ? 'active' : 'inactive'}
91
+ label={enabled ? 'Active' : 'Inactive'}
92
+ />
93
+ </ConfigCardHeader>
97
94
 
98
- {/* Status Badge */}
99
- {enabled ? (
100
- <div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-50 text-emerald-600">
101
- <Zap className="h-3.5 w-3.5" />
102
- <span className="text-[11px] font-bold">Active</span>
103
- </div>
104
- ) : (
105
- <div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-gray-100 text-gray-500">
106
- <Radio className="h-3.5 w-3.5" />
107
- <span className="text-[11px] font-bold">Inactive</span>
108
- </div>
109
- )}
110
- </div>
111
-
112
- {/* Channel Info */}
113
- <div className="flex-1">
114
- <h3 className="text-[15px] font-bold text-gray-900 mb-1">
115
- {channel.displayName || channel.name}
116
- </h3>
117
- <p className="text-[12px] text-gray-500 leading-relaxed line-clamp-2">
118
- {description}
119
- </p>
120
- </div>
95
+ <ConfigCardBody
96
+ title={channel.displayName || channel.name}
97
+ description={description}
98
+ />
121
99
 
122
- {/* Footer with Actions */}
123
- <div className="mt-4 pt-3 flex items-center justify-between">
124
- <span className="inline-flex items-center gap-1 text-[13px] font-semibold text-gray-600 group-hover:text-primary transition-colors cursor-pointer">
125
- {enabled ? 'Configure' : 'Enable'}
126
- <ArrowRight className="h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" />
127
- </span>
100
+ <ConfigCardFooter>
101
+ <ActionLink label={enabled ? 'Configure' : 'Enable'} />
128
102
  {channel.tutorialUrl && (
129
103
  <a
130
104
  href={channel.tutorialUrl}
131
105
  target="_blank"
132
106
  rel="noreferrer"
133
107
  onClick={(e) => e.stopPropagation()}
134
- className="flex items-center justify-center h-7 w-7 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 transition-colors"
108
+ className="flex items-center justify-center h-6 w-6 rounded-md text-gray-300 hover:text-gray-500 hover:bg-gray-100/60 transition-colors"
135
109
  title="View Guide"
136
110
  >
137
111
  <ExternalLink className="h-3.5 w-3.5" />
138
112
  </a>
139
113
  )}
140
- </div>
141
- </div>
114
+ </ConfigCardFooter>
115
+ </ConfigCard>
142
116
  );
143
117
  })}
144
118
  </div>
@@ -146,14 +120,14 @@ export function ChannelsList() {
146
120
  {/* Empty State */}
147
121
  {filteredChannels.length === 0 && (
148
122
  <div className="flex flex-col items-center justify-center py-16 text-center">
149
- <div className="h-16 w-16 flex items-center justify-center rounded-2xl bg-gray-100 mb-4">
150
- <MessageSquare className="h-8 w-8 text-gray-400" />
123
+ <div className="h-14 w-14 flex items-center justify-center rounded-xl bg-gray-100/80 mb-4">
124
+ <MessageSquare className="h-6 w-6 text-gray-300" />
151
125
  </div>
152
- <h3 className="text-[15px] font-bold text-gray-900 mb-2">
126
+ <h3 className="text-[14px] font-semibold text-gray-900 mb-1.5">
153
127
  No channels enabled
154
128
  </h3>
155
- <p className="text-[13px] text-gray-500 max-w-sm">
156
- Enable a messaging channel to start receiving messages. Click on any channel to configure it.
129
+ <p className="text-[13px] text-gray-400 max-w-sm">
130
+ Enable a messaging channel to start receiving messages.
157
131
  </p>
158
132
  </div>
159
133
  )}
@@ -144,7 +144,7 @@ export function CronConfig() {
144
144
  <div className="h-[calc(100vh-80px)] w-full max-w-[1200px] mx-auto animate-fade-in flex flex-col pt-6 pb-2">
145
145
  <div className="flex items-center justify-between mb-6 shrink-0">
146
146
  <div>
147
- <h2 className="text-2xl font-bold text-gray-900 tracking-tight">{t('cronPageTitle')}</h2>
147
+ <h2 className="text-xl font-semibold text-gray-900 tracking-tight">{t('cronPageTitle')}</h2>
148
148
  <p className="text-sm text-gray-500 mt-1">{t('cronPageDescription')}</p>
149
149
  </div>
150
150
  <Button
@@ -157,36 +157,34 @@ export function CronConfig() {
157
157
  </Button>
158
158
  </div>
159
159
 
160
- <Card hover={false} className="mb-6">
161
- <CardContent className="pt-6">
162
- <div className="flex flex-wrap gap-3 items-center">
163
- <div className="relative flex-1 min-w-[240px]">
164
- <Input
165
- value={query}
166
- onChange={(e) => setQuery(e.target.value)}
167
- placeholder={t('cronSearchPlaceholder')}
168
- className="pl-9"
169
- />
170
- <AlarmClock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" />
171
- </div>
172
- <div className="min-w-[180px]">
173
- <Select value={status} onValueChange={(value) => setStatus(value as StatusFilter)}>
174
- <SelectTrigger className="w-full">
175
- <SelectValue placeholder={t('cronStatusLabel')} />
176
- </SelectTrigger>
177
- <SelectContent>
178
- <SelectItem value="all">{t('cronStatusAll')}</SelectItem>
179
- <SelectItem value="enabled">{t('cronStatusEnabled')}</SelectItem>
180
- <SelectItem value="disabled">{t('cronStatusDisabled')}</SelectItem>
181
- </SelectContent>
182
- </Select>
183
- </div>
184
- <div className="text-xs text-gray-500 ml-auto">
185
- {t('cronTotalLabel')}: {cronQuery.data?.total ?? 0} / {jobs.length}
186
- </div>
160
+ <div className="mb-6">
161
+ <div className="flex flex-wrap gap-3 items-center">
162
+ <div className="relative flex-1 min-w-[240px]">
163
+ <Input
164
+ value={query}
165
+ onChange={(e) => setQuery(e.target.value)}
166
+ placeholder={t('cronSearchPlaceholder')}
167
+ className="pl-9"
168
+ />
169
+ <AlarmClock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" />
187
170
  </div>
188
- </CardContent>
189
- </Card>
171
+ <div className="min-w-[180px]">
172
+ <Select value={status} onValueChange={(value) => setStatus(value as StatusFilter)}>
173
+ <SelectTrigger className="w-full">
174
+ <SelectValue placeholder={t('cronStatusLabel')} />
175
+ </SelectTrigger>
176
+ <SelectContent>
177
+ <SelectItem value="all">{t('cronStatusAll')}</SelectItem>
178
+ <SelectItem value="enabled">{t('cronStatusEnabled')}</SelectItem>
179
+ <SelectItem value="disabled">{t('cronStatusDisabled')}</SelectItem>
180
+ </SelectContent>
181
+ </Select>
182
+ </div>
183
+ <div className="text-xs text-gray-500 ml-auto">
184
+ {t('cronTotalLabel')}: {cronQuery.data?.total ?? 0} / {jobs.length}
185
+ </div>
186
+ </div>
187
+ </div>
190
188
 
191
189
  <div className="flex-1 overflow-auto custom-scrollbar">
192
190
  {cronQuery.isLoading ? (
@@ -69,7 +69,7 @@ export function ModelConfig() {
69
69
  return (
70
70
  <div className="max-w-4xl animate-fade-in pb-20">
71
71
  <div className="mb-10">
72
- <h2 className="text-2xl font-bold text-gray-900">Model Configuration</h2>
72
+ <h2 className="text-xl font-semibold text-gray-900">Model Configuration</h2>
73
73
  <p className="text-sm text-gray-500 mt-1">Configure default AI model and runtime limits</p>
74
74
  </div>
75
75
 
@@ -1,5 +1,5 @@
1
1
  import { useConfig, useConfigMeta, useConfigSchema } from '@/hooks/useConfig';
2
- import { KeyRound, Check, Settings, ArrowRight } from 'lucide-react';
2
+ import { KeyRound } from 'lucide-react';
3
3
  import { useState } from 'react';
4
4
  import { ProviderForm } from './ProviderForm';
5
5
  import { useUiStore } from '@/stores/ui.store';
@@ -8,6 +8,9 @@ import { Tabs } from '@/components/ui/tabs-custom';
8
8
  import { LogoBadge } from '@/components/common/LogoBadge';
9
9
  import { getProviderLogo } from '@/lib/logos';
10
10
  import { hintForPath } from '@/lib/config-hints';
11
+ import { ConfigCard, ConfigCardHeader, ConfigCardBody, ConfigCardFooter } from '@/components/ui/config-card';
12
+ import { StatusDot } from '@/components/ui/status-dot';
13
+ import { ActionLink } from '@/components/ui/action-link';
11
14
 
12
15
  export function ProvidersList() {
13
16
  const { data: config } = useConfig();
@@ -32,8 +35,8 @@ export function ProvidersList() {
32
35
 
33
36
  return (
34
37
  <div className="animate-fade-in pb-20">
35
- <div className="flex items-center justify-between mb-8">
36
- <h2 className="text-2xl font-bold text-gray-900">AI Providers</h2>
38
+ <div className="flex items-center justify-between mb-6">
39
+ <h2 className="text-xl font-semibold text-gray-900">AI Providers</h2>
37
40
  </div>
38
41
 
39
42
  <Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} />
@@ -47,71 +50,42 @@ export function ProvidersList() {
47
50
  const description = providerHint?.help || 'Configure AI services for your agents';
48
51
 
49
52
  return (
50
- <div
51
- key={provider.name}
52
- className={cn(
53
- 'group relative flex-col p-5 rounded-2xl border transition-all duration-base cursor-pointer',
54
- 'hover:shadow-card-hover hover:-translate-y-0.5',
55
- hasConfig
56
- ? 'bg-white border-gray-200 shadow-sm'
57
- : 'bg-white border-transparent shadow-sm hover:border-gray-200'
58
- )}
59
- onClick={() => openProviderModal(provider.name)}
60
- >
61
- {/* Header with Logo and Status */}
62
- <div className="flex items-start justify-between mb-4">
53
+ <ConfigCard key={provider.name} onClick={() => openProviderModal(provider.name)}>
54
+ <ConfigCardHeader>
63
55
  <LogoBadge
64
56
  name={provider.name}
65
57
  src={getProviderLogo(provider.name)}
66
58
  className={cn(
67
- 'h-12 w-12 rounded-xl border transition-all',
59
+ 'h-11 w-11 rounded-xl border transition-all',
68
60
  hasConfig
69
- ? 'bg-white border-primary'
70
- : 'bg-white border-gray-200 group-hover:border-gray-300'
61
+ ? 'bg-white border-primary/30'
62
+ : 'bg-white border-gray-200/60 group-hover:border-gray-300'
71
63
  )}
72
- imgClassName="h-7 w-7"
64
+ imgClassName="h-6 w-6"
73
65
  fallback={(
74
66
  <span className={cn(
75
- 'text-lg font-bold uppercase',
76
- hasConfig ? 'text-gray-900' : 'text-gray-500'
67
+ 'text-base font-semibold uppercase',
68
+ hasConfig ? 'text-gray-800' : 'text-gray-400'
77
69
  )}>
78
70
  {provider.name[0]}
79
71
  </span>
80
72
  )}
81
73
  />
74
+ <StatusDot
75
+ status={hasConfig ? 'ready' : 'setup'}
76
+ label={hasConfig ? 'Ready' : 'Setup'}
77
+ />
78
+ </ConfigCardHeader>
82
79
 
83
- {/* Status Badge */}
84
- {hasConfig ? (
85
- <div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-50 text-emerald-600">
86
- <Check className="h-3.5 w-3.5" />
87
- <span className="text-[11px] font-bold">Ready</span>
88
- </div>
89
- ) : (
90
- <div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-gray-100 text-gray-500">
91
- <Settings className="h-3.5 w-3.5" />
92
- <span className="text-[11px] font-bold">Setup</span>
93
- </div>
94
- )}
95
- </div>
96
-
97
- {/* Provider Info */}
98
- <div className="flex-1">
99
- <h3 className="text-[15px] font-bold text-gray-900 mb-1">
100
- {provider.displayName || provider.name}
101
- </h3>
102
- <p className="text-[12px] text-gray-500 leading-relaxed line-clamp-2">
103
- {description}
104
- </p>
105
- </div>
80
+ <ConfigCardBody
81
+ title={provider.displayName || provider.name}
82
+ description={description}
83
+ />
106
84
 
107
- {/* Footer with Action */}
108
- <div className="mt-4 pt-3">
109
- <span className="inline-flex items-center gap-1 text-[13px] font-semibold text-gray-600 group-hover:text-primary transition-colors cursor-pointer">
110
- {hasConfig ? 'Configure' : 'Add Provider'}
111
- <ArrowRight className="h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" />
112
- </span>
113
- </div>
114
- </div>
85
+ <ConfigCardFooter>
86
+ <ActionLink label={hasConfig ? 'Configure' : 'Add Provider'} />
87
+ </ConfigCardFooter>
88
+ </ConfigCard>
115
89
  );
116
90
  })}
117
91
  </div>
@@ -119,14 +93,14 @@ export function ProvidersList() {
119
93
  {/* Empty State */}
120
94
  {filteredProviders.length === 0 && (
121
95
  <div className="flex flex-col items-center justify-center py-16 text-center">
122
- <div className="h-16 w-16 flex items-center justify-center rounded-2xl bg-gray-100 mb-4">
123
- <KeyRound className="h-8 w-8 text-gray-400" />
96
+ <div className="h-14 w-14 flex items-center justify-center rounded-xl bg-gray-100/80 mb-4">
97
+ <KeyRound className="h-6 w-6 text-gray-300" />
124
98
  </div>
125
- <h3 className="text-[15px] font-bold text-gray-900 mb-2">
99
+ <h3 className="text-[14px] font-semibold text-gray-900 mb-1.5">
126
100
  No providers configured
127
101
  </h3>
128
- <p className="text-[13px] text-gray-500 max-w-sm">
129
- Add an AI provider to start using the platform. Click on any provider to configure it.
102
+ <p className="text-[13px] text-gray-400 max-w-sm">
103
+ Add an AI provider to start using the platform.
130
104
  </p>
131
105
  </div>
132
106
  )}
@@ -232,7 +232,7 @@ export function RuntimeConfig() {
232
232
  return (
233
233
  <div className="space-y-6 pb-20 animate-fade-in">
234
234
  <div>
235
- <h2 className="text-2xl font-bold text-gray-900">Routing & Runtime</h2>
235
+ <h2 className="text-xl font-semibold text-gray-900">Routing & Runtime</h2>
236
236
  <p className="text-sm text-gray-500 mt-1">
237
237
  Align multi-agent routing with OpenClaw: bindings, agent pool, and DM scope.
238
238
  </p>
@@ -224,7 +224,7 @@ export function SessionsConfig() {
224
224
 
225
225
  <div className="flex items-center justify-between mb-6 shrink-0">
226
226
  <div>
227
- <h2 className="text-2xl font-bold text-gray-900 tracking-tight">{t('sessionsPageTitle')}</h2>
227
+ <h2 className="text-xl font-semibold text-gray-900 tracking-tight">{t('sessionsPageTitle')}</h2>
228
228
  <p className="text-sm text-gray-500 mt-1">{t('sessionsPageDescription')}</p>
229
229
  </div>
230
230
  </div>
@@ -10,11 +10,11 @@ function AppLayoutInner({ children }: AppLayoutProps) {
10
10
  useDocLinkInterceptor();
11
11
 
12
12
  return (
13
- <div className="h-screen flex bg-white font-sans text-foreground">
13
+ <div className="h-screen flex bg-background font-sans text-foreground">
14
14
  <Sidebar />
15
15
  <div className="flex-1 flex min-w-0 overflow-hidden relative">
16
- <div className="flex-1 flex flex-col min-w-0 bg-gray-50 overflow-hidden">
17
- <main className="flex-1 overflow-auto custom-scrollbar p-10">
16
+ <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
17
+ <main className="flex-1 overflow-auto custom-scrollbar p-8">
18
18
  <div className="max-w-6xl mx-auto animate-fade-in h-full">
19
19
  {children}
20
20
  </div>
@@ -1,5 +1,3 @@
1
- import { Bell, Search } from 'lucide-react';
2
-
3
1
  interface HeaderProps {
4
2
  title?: string;
5
3
  description?: string;
@@ -7,30 +5,17 @@ interface HeaderProps {
7
5
 
8
6
  export function Header({ title, description }: HeaderProps) {
9
7
  return (
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
- <div className="flex items-center gap-4">
8
+ <header className="h-14 bg-white/90 backdrop-blur-sm sticky top-0 z-10 flex items-center px-6 transition-all duration-base">
9
+ <div className="flex items-center gap-3">
12
10
  {title && (
13
11
  <div>
14
- <h2 className="text-base font-semibold text-gray-900">{title}</h2>
12
+ <h2 className="text-[15px] font-semibold text-gray-900">{title}</h2>
15
13
  {description && (
16
- <p className="text-xs text-gray-500">{description}</p>
14
+ <p className="text-xs text-gray-400 mt-0.5">{description}</p>
17
15
  )}
18
16
  </div>
19
17
  )}
20
18
  </div>
21
-
22
- <div className="flex items-center gap-3">
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
- <Search className="h-4 w-4" />
25
- </button>
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
- <Bell className="h-4 w-4" />
28
- <span className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-primary" />
29
- </button>
30
- <div className="h-9 w-9 rounded-lg bg-gradient-to-br from-primary to-primary-600 flex items-center justify-center">
31
- <span className="text-xs font-semibold text-white">N</span>
32
- </div>
33
- </div>
34
19
  </header>
35
20
  );
36
21
  }
@@ -46,14 +46,14 @@ export function Sidebar() {
46
46
  const docBrowser = useDocBrowser();
47
47
 
48
48
  return (
49
- <aside className="w-[240px] bg-white border-r border-gray-200 flex flex-col h-full py-6 px-4">
49
+ <aside className="w-[240px] bg-[#f0f2f7] flex flex-col h-full py-6 px-4">
50
50
  {/* Logo Area */}
51
- <div className="px-3 mb-8">
52
- <div className="flex items-center gap-2.5 group cursor-pointer">
53
- <div className="h-7 w-7 rounded-lg overflow-hidden flex items-center justify-center transition-transform duration-fast group-hover:scale-110">
51
+ <div className="px-2 mb-8">
52
+ <div className="flex items-center gap-2.5 cursor-pointer">
53
+ <div className="h-7 w-7 rounded-lg overflow-hidden flex items-center justify-center">
54
54
  <img src="/logo.svg" alt="NextClaw" className="h-full w-full object-contain" />
55
55
  </div>
56
- <span className="text-[15px] font-bold bg-gradient-to-r from-primary to-indigo-500 bg-clip-text text-transparent tracking-[-0.02em]">NextClaw</span>
56
+ <span className="text-[15px] font-semibold text-gray-800 tracking-[-0.01em]">NextClaw</span>
57
57
  </div>
58
58
  </div>
59
59
 
@@ -68,17 +68,17 @@ export function Sidebar() {
68
68
  <NavLink
69
69
  to={item.target}
70
70
  className={({ isActive }) => cn(
71
- 'group w-full flex items-center gap-3 px-4 py-2.5 rounded-lg text-sm font-medium transition-all duration-base',
71
+ 'group w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-[14px] font-medium transition-all duration-base',
72
72
  isActive
73
- ? 'bg-brand-50 text-brand-700'
74
- : 'text-gray-600 hover:bg-gray-100/80 hover:text-gray-900'
73
+ ? 'bg-primary/10 text-primary font-semibold'
74
+ : 'text-gray-600 hover:bg-[#e4e7ef] hover:text-gray-800'
75
75
  )}
76
76
  >
77
77
  {({ isActive }) => (
78
78
  <>
79
79
  <Icon className={cn(
80
- 'h-4 w-4 transition-transform duration-fast group-hover:scale-110',
81
- isActive ? 'text-primary' : 'text-gray-500'
80
+ 'h-[17px] w-[17px] transition-colors',
81
+ isActive ? 'text-primary' : 'text-gray-400'
82
82
  )} />
83
83
  <span className="flex-1 text-left">{item.label}</span>
84
84
  </>
@@ -91,20 +91,12 @@ export function Sidebar() {
91
91
  </nav>
92
92
 
93
93
  {/* Help Button */}
94
- <div className="pt-2 border-t border-gray-100 mt-2">
94
+ <div className="pt-3 border-t border-[#dde0ea] mt-3">
95
95
  <button
96
96
  onClick={() => docBrowser.open()}
97
- className={cn(
98
- 'w-full flex items-center gap-3 px-4 py-2.5 rounded-lg text-sm font-medium transition-all duration-base',
99
- docBrowser.isOpen
100
- ? 'bg-brand-50 text-brand-700'
101
- : 'text-gray-600 hover:bg-gray-100/80 hover:text-gray-900'
102
- )}
97
+ className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-[14px] font-medium transition-all duration-base text-gray-600 hover:bg-[#e4e7ef] hover:text-gray-800"
103
98
  >
104
- <BookOpen className={cn(
105
- 'h-4 w-4',
106
- docBrowser.isOpen ? 'text-primary' : 'text-gray-500'
107
- )} />
99
+ <BookOpen className="h-[17px] w-[17px] text-gray-400" />
108
100
  <span className="flex-1 text-left">{t('docBrowserHelp')}</span>
109
101
  </button>
110
102
  </div>