@nextclaw/ui 0.2.1

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 (48) hide show
  1. package/.eslintrc.cjs +28 -0
  2. package/CHANGELOG.md +7 -0
  3. package/dist/assets/index-BrN4G7FO.js +240 -0
  4. package/dist/assets/index-VjHB2nG6.css +1 -0
  5. package/dist/index.html +14 -0
  6. package/index.html +13 -0
  7. package/package.json +50 -0
  8. package/postcss.config.js +6 -0
  9. package/src/App.tsx +51 -0
  10. package/src/api/client.ts +40 -0
  11. package/src/api/config.ts +86 -0
  12. package/src/api/types.ts +78 -0
  13. package/src/api/websocket.ts +77 -0
  14. package/src/components/common/KeyValueEditor.tsx +65 -0
  15. package/src/components/common/MaskedInput.tsx +39 -0
  16. package/src/components/common/StatusBadge.tsx +56 -0
  17. package/src/components/common/TagInput.tsx +56 -0
  18. package/src/components/config/ChannelForm.tsx +259 -0
  19. package/src/components/config/ChannelsList.tsx +102 -0
  20. package/src/components/config/ModelConfig.tsx +181 -0
  21. package/src/components/config/ProviderForm.tsx +147 -0
  22. package/src/components/config/ProvidersList.tsx +90 -0
  23. package/src/components/config/UiConfig.tsx +189 -0
  24. package/src/components/layout/AppLayout.tsx +20 -0
  25. package/src/components/layout/Header.tsx +36 -0
  26. package/src/components/layout/Sidebar.tsx +103 -0
  27. package/src/components/ui/HighlightCard.tsx +40 -0
  28. package/src/components/ui/button.tsx +50 -0
  29. package/src/components/ui/card.tsx +78 -0
  30. package/src/components/ui/dialog.tsx +120 -0
  31. package/src/components/ui/input.tsx +23 -0
  32. package/src/components/ui/label.tsx +20 -0
  33. package/src/components/ui/scroll-area.tsx +21 -0
  34. package/src/components/ui/skeleton.tsx +15 -0
  35. package/src/components/ui/switch.tsx +37 -0
  36. package/src/components/ui/tabs-custom.tsx +45 -0
  37. package/src/components/ui/tabs.tsx +88 -0
  38. package/src/hooks/useConfig.ts +95 -0
  39. package/src/hooks/useWebSocket.ts +38 -0
  40. package/src/index.css +177 -0
  41. package/src/lib/i18n.ts +119 -0
  42. package/src/lib/utils.ts +6 -0
  43. package/src/main.tsx +10 -0
  44. package/src/stores/ui.store.ts +39 -0
  45. package/src/vite-env.d.ts +9 -0
  46. package/tailwind.config.js +43 -0
  47. package/tsconfig.json +18 -0
  48. package/vite.config.ts +25 -0
package/src/index.css ADDED
@@ -0,0 +1,177 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ /* HappyCapy-inspired cleaner "Milk/Cream" palette */
8
+ --background: 40 20% 98%;
9
+ /* Very light cream #F9F9F7 */
10
+ --foreground: 30 15% 10%;
11
+ /* Very dark brown/gray */
12
+ --card: 0 0% 100%;
13
+ /* Pure white cards */
14
+ --card-foreground: 30 15% 10%;
15
+ --popover: 0 0% 100%;
16
+ --popover-foreground: 30 15% 10%;
17
+ --primary: 30 15% 10%;
18
+ /* Dark active states */
19
+ --primary-foreground: 0 0% 100%;
20
+ --secondary: 40 10% 94%;
21
+ /* Neutral light gray */
22
+ --secondary-foreground: 30 15% 10%;
23
+ --muted: 40 10% 94%;
24
+ --muted-foreground: 30 8% 45%;
25
+ --accent: 40 10% 92%;
26
+ /* Light pill background */
27
+ --accent-foreground: 30 15% 10%;
28
+ --destructive: 0 84% 60%;
29
+ --destructive-foreground: 0 0% 98%;
30
+ --border: 40 10% 92%;
31
+ --input: 40 10% 92%;
32
+ --ring: 30 15% 10%;
33
+ --radius: 1.25rem;
34
+ /* Large rounded corners (20px) */
35
+
36
+ /* HappyCapy Neutrals */
37
+ --milk-50: 40 20% 98%;
38
+ --milk-100: 40 15% 96%;
39
+ --milk-200: 40 12% 92%;
40
+ --milk-300: 40 10% 88%;
41
+ --milk-400: 40 8% 70%;
42
+ --milk-500: 30 8% 45%;
43
+ --milk-600: 30 10% 35%;
44
+ --milk-700: 30 12% 25%;
45
+ --milk-800: 30 15% 15%;
46
+ --milk-900: 30 20% 8%;
47
+ }
48
+ }
49
+
50
+ @layer base {
51
+ * {
52
+ @apply border-border outline-none transition-colors duration-200;
53
+ }
54
+
55
+ html {
56
+ @apply antialiased;
57
+ -webkit-font-smoothing: antialiased;
58
+ -moz-osx-font-smoothing: grayscale;
59
+ }
60
+
61
+ body {
62
+ @apply bg-background text-foreground;
63
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
64
+ overflow: hidden;
65
+ }
66
+
67
+ /* Smooth scrolling for the whole page if needed */
68
+ * {
69
+ scrollbar-width: thin;
70
+ scrollbar-color: hsl(var(--warm-gray-300)) transparent;
71
+ }
72
+ }
73
+
74
+ @layer utilities {
75
+
76
+ /* Custom scrollbar */
77
+ .custom-scrollbar::-webkit-scrollbar {
78
+ width: 6px;
79
+ height: 6px;
80
+ }
81
+
82
+ .custom-scrollbar::-webkit-scrollbar-track {
83
+ background: transparent;
84
+ }
85
+
86
+ .custom-scrollbar::-webkit-scrollbar-thumb {
87
+ background: hsl(var(--warm-gray-300));
88
+ border-radius: 6px;
89
+ }
90
+
91
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
92
+ background: hsl(var(--warm-gray-400));
93
+ }
94
+
95
+ /* Glassmorphism */
96
+ .glass {
97
+ @apply bg-white/70 backdrop-blur-md border border-white/20;
98
+ }
99
+
100
+ .glass-dark {
101
+ @apply bg-black/10 backdrop-blur-md border border-white/10;
102
+ }
103
+
104
+ /* Premium Shadows */
105
+ .shadow-premium {
106
+ box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.08), 0 4px 10px -4px rgba(0, 0, 0, 0.04);
107
+ }
108
+
109
+ .shadow-premium-hover {
110
+ box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.12), 0 8px 15px -6px rgba(0, 0, 0, 0.06);
111
+ }
112
+ }
113
+
114
+ /* Animation keyframes */
115
+ @keyframes fadeIn {
116
+ from {
117
+ opacity: 0;
118
+ transform: translateY(12px);
119
+ }
120
+
121
+ to {
122
+ opacity: 1;
123
+ transform: translateY(0);
124
+ }
125
+ }
126
+
127
+ @keyframes slideIn {
128
+ from {
129
+ opacity: 0;
130
+ transform: translateX(-12px);
131
+ }
132
+
133
+ to {
134
+ opacity: 1;
135
+ transform: translateX(0);
136
+ }
137
+ }
138
+
139
+ @keyframes scaleIn {
140
+ from {
141
+ opacity: 0;
142
+ transform: scale(0.97);
143
+ }
144
+
145
+ to {
146
+ opacity: 1;
147
+ transform: scale(1);
148
+ }
149
+ }
150
+
151
+ @keyframes pulse-soft {
152
+
153
+ 0%,
154
+ 100% {
155
+ opacity: 1;
156
+ }
157
+
158
+ 50% {
159
+ opacity: 0.8;
160
+ }
161
+ }
162
+
163
+ .animate-fade-in {
164
+ animation: fadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
165
+ }
166
+
167
+ .animate-slide-in {
168
+ animation: slideIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
169
+ }
170
+
171
+ .animate-scale-in {
172
+ animation: scaleIn 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;
173
+ }
174
+
175
+ .animate-pulse-soft {
176
+ animation: pulse-soft 3s ease-in-out infinite;
177
+ }
@@ -0,0 +1,119 @@
1
+ // i18n labels - Chinese primary, English fallback
2
+ export const LABELS: Record<string, { zh: string; en: string }> = {
3
+ // Navigation
4
+ model: { zh: '模型', en: 'Model' },
5
+ providers: { zh: '提供商', en: 'Providers' },
6
+ channels: { zh: '渠道', en: 'Channels' },
7
+ uiConfig: { zh: '界面', en: 'UI' },
8
+
9
+ // Common
10
+ enabled: { zh: '启用', en: 'Enabled' },
11
+ disabled: { zh: '禁用', en: 'Disabled' },
12
+ save: { zh: '保存', en: 'Save' },
13
+ cancel: { zh: '取消', en: 'Cancel' },
14
+ delete: { zh: '删除', en: 'Delete' },
15
+ add: { zh: '添加', en: 'Add' },
16
+ edit: { zh: '编辑', en: 'Edit' },
17
+ loading: { zh: '加载中...', en: 'Loading...' },
18
+ success: { zh: '成功', en: 'Success' },
19
+ error: { zh: '错误', en: 'Error' },
20
+
21
+ // Model
22
+ modelName: { zh: '模型', en: 'Model' },
23
+ workspace: { zh: '工作空间', en: 'Workspace' },
24
+ maxTokens: { zh: '最大 Token 数', en: 'Max Tokens' },
25
+ temperature: { zh: '温度', en: 'Temperature' },
26
+ maxToolIterations: { zh: '最大工具迭代次数', en: 'Max Tool Iterations' },
27
+
28
+ // Provider
29
+ apiKey: { zh: 'API 密钥', en: 'API Key' },
30
+ apiBase: { zh: 'API Base', en: 'API Base' },
31
+ extraHeaders: { zh: '额外请求头', en: 'Extra Headers' },
32
+ apiKeySet: { zh: '已设置', en: 'Set' },
33
+ apiKeyNotSet: { zh: '未设置', en: 'Not Set' },
34
+ showKey: { zh: '显示密钥', en: 'Show Key' },
35
+ hideKey: { zh: '隐藏密钥', en: 'Hide Key' },
36
+
37
+ // Channel
38
+ allowFrom: { zh: '允许来源', en: 'Allow From' },
39
+ token: { zh: 'Token', en: 'Token' },
40
+ botToken: { zh: 'Bot Token', en: 'Bot Token' },
41
+ appToken: { zh: 'App Token', en: 'App Token' },
42
+ appId: { zh: 'App ID', en: 'App ID' },
43
+ appSecret: { zh: 'App Secret', en: 'App Secret' },
44
+ clientId: { zh: 'Client ID', en: 'Client ID' },
45
+ clientSecret: { zh: 'Client Secret', en: 'Client Secret' },
46
+ encryptKey: { zh: '加密密钥', en: 'Encrypt Key' },
47
+ verificationToken: { zh: '验证令牌', en: 'Verification Token' },
48
+ bridgeUrl: { zh: '桥接 URL', en: 'Bridge URL' },
49
+ gatewayUrl: { zh: '网关 URL', en: 'Gateway URL' },
50
+ proxy: { zh: '代理', en: 'Proxy' },
51
+ intents: { zh: 'Intents', en: 'Intents' },
52
+ mode: { zh: '模式', en: 'Mode' },
53
+ webhookPath: { zh: 'Webhook 路径', en: 'Webhook Path' },
54
+ groupPolicy: { zh: '群组策略', en: 'Group Policy' },
55
+ consentGranted: { zh: '同意条款', en: 'Consent Granted' },
56
+ imapHost: { zh: 'IMAP 服务器', en: 'IMAP Host' },
57
+ imapPort: { zh: 'IMAP 端口', en: 'IMAP Port' },
58
+ imapUsername: { zh: 'IMAP 用户名', en: 'IMAP Username' },
59
+ imapPassword: { zh: 'IMAP 密码', en: 'IMAP Password' },
60
+ imapMailbox: { zh: 'IMAP 邮箱', en: 'IMAP Mailbox' },
61
+ imapUseSsl: { zh: 'IMAP 使用 SSL', en: 'IMAP Use SSL' },
62
+ smtpHost: { zh: 'SMTP 服务器', en: 'SMTP Host' },
63
+ smtpPort: { zh: 'SMTP 端口', en: 'SMTP Port' },
64
+ smtpUsername: { zh: 'SMTP 用户名', en: 'SMTP Username' },
65
+ smtpPassword: { zh: 'SMTP 密码', en: 'SMTP Password' },
66
+ smtpUseTls: { zh: 'SMTP 使用 TLS', en: 'SMTP Use TLS' },
67
+ smtpUseSsl: { zh: 'SMTP 使用 SSL', en: 'SMTP Use SSL' },
68
+ fromAddress: { zh: '发件地址', en: 'From Address' },
69
+ autoReplyEnabled: { zh: '自动回复已启用', en: 'Auto Reply Enabled' },
70
+ pollIntervalSeconds: { zh: '轮询间隔(秒)', en: 'Poll Interval (s)' },
71
+ markSeen: { zh: '标记为已读', en: 'Mark Seen' },
72
+ maxBodyChars: { zh: '最大正文字符数', en: 'Max Body Chars' },
73
+ subjectPrefix: { zh: '主题前缀', en: 'Subject Prefix' },
74
+ baseUrl: { zh: 'Base URL', en: 'Base URL' },
75
+ socketUrl: { zh: 'Socket URL', en: 'Socket URL' },
76
+ socketPath: { zh: 'Socket 路径', en: 'Socket Path' },
77
+ socketDisableMsgpack: { zh: '禁用 Msgpack', en: 'Disable Msgpack' },
78
+ socketReconnectDelayMs: { zh: '重连延迟(ms)', en: 'Reconnect Delay (ms)' },
79
+ socketMaxReconnectDelayMs: { zh: '最大重连延迟(ms)', en: 'Max Reconnect Delay (ms)' },
80
+ socketConnectTimeoutMs: { zh: '连接超时(ms)', en: 'Connect Timeout (ms)' },
81
+ refreshIntervalMs: { zh: '刷新间隔(ms)', en: 'Refresh Interval (ms)' },
82
+ watchTimeoutMs: { zh: '监视超时(ms)', en: 'Watch Timeout (ms)' },
83
+ watchLimit: { zh: '监视限制', en: 'Watch Limit' },
84
+ retryDelayMs: { zh: '重试延迟(ms)', en: 'Retry Delay (ms)' },
85
+ maxRetryAttempts: { zh: '最大重试次数', en: 'Max Retry Attempts' },
86
+ clawToken: { zh: 'Claw Token', en: 'Claw Token' },
87
+ agentUserId: { zh: '代理用户ID', en: 'Agent User ID' },
88
+ sessions: { zh: '会话', en: 'Sessions' },
89
+ panels: { zh: '面板', en: 'Panels' },
90
+ mentionRequireInGroups: { zh: '群组中需要@', en: 'Require Mention in Groups' },
91
+ groups: { zh: '群组', en: 'Groups' },
92
+ replyDelayMode: { zh: '回复延迟模式', en: 'Reply Delay Mode' },
93
+ replyDelayMs: { zh: '回复延迟(ms)', en: 'Reply Delay (ms)' },
94
+ secret: { zh: '密钥', en: 'Secret' },
95
+
96
+ // UI Config
97
+ host: { zh: '主机', en: 'Host' },
98
+ port: { zh: '端口', en: 'Port' },
99
+ open: { zh: '自动打开', en: 'Open Automatically' },
100
+ reloadConfig: { zh: '重载配置', en: 'Reload Config' },
101
+
102
+ // Status
103
+ connected: { zh: '已连接', en: 'Connected' },
104
+ disconnected: { zh: '未连接', en: 'Disconnected' },
105
+ connecting: { zh: '连接中...', en: 'Connecting...' },
106
+
107
+ // Messages
108
+ configSaved: { zh: '配置已保存', en: 'Configuration saved' },
109
+ configSaveFailed: { zh: '保存配置失败', en: 'Failed to save configuration' },
110
+ configReloaded: { zh: '配置已重载', en: 'Configuration reloaded' },
111
+ configReloadFailed: { zh: '重载配置失败', en: 'Failed to reload configuration' },
112
+ enterTag: { zh: '输入后按回车...', en: 'Type and press Enter...' },
113
+ headerName: { zh: 'Header 名称', en: 'Header Name' },
114
+ headerValue: { zh: 'Header 值', en: 'Header Value' }
115
+ };
116
+
117
+ export function t(key: string, lang: 'zh' | 'en' = 'en'): string {
118
+ return LABELS[key]?.[lang] || LABELS[key]?.en || key;
119
+ }
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
package/src/main.tsx ADDED
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import App from './App';
4
+ import './index.css';
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>
10
+ );
@@ -0,0 +1,39 @@
1
+ import { create } from 'zustand';
2
+
3
+ type ConnectionStatus = 'connected' | 'disconnected' | 'connecting';
4
+
5
+ interface UiState {
6
+ // Active configuration tab
7
+ activeTab: 'model' | 'providers' | 'channels' | 'ui';
8
+ setActiveTab: (tab: UiState['activeTab']) => void;
9
+
10
+ // Connection status
11
+ connectionStatus: ConnectionStatus;
12
+ setConnectionStatus: (status: ConnectionStatus) => void;
13
+
14
+ // Provider modal
15
+ providerModal: { open: boolean; provider?: string };
16
+ openProviderModal: (provider?: string) => void;
17
+ closeProviderModal: () => void;
18
+
19
+ // Channel modal
20
+ channelModal: { open: boolean; channel?: string };
21
+ openChannelModal: (channel?: string) => void;
22
+ closeChannelModal: () => void;
23
+ }
24
+
25
+ export const useUiStore = create<UiState>((set) => ({
26
+ activeTab: 'model',
27
+ setActiveTab: (tab) => set({ activeTab: tab }),
28
+
29
+ connectionStatus: 'disconnected',
30
+ setConnectionStatus: (status) => set({ connectionStatus: status }),
31
+
32
+ providerModal: { open: false },
33
+ openProviderModal: (provider) => set({ providerModal: { open: true, provider } }),
34
+ closeProviderModal: () => set({ providerModal: { open: false } }),
35
+
36
+ channelModal: { open: false },
37
+ openChannelModal: (channel) => set({ channelModal: { open: true, channel } }),
38
+ closeChannelModal: () => set({ channelModal: { open: false } })
39
+ }));
@@ -0,0 +1,9 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_API_BASE?: string;
5
+ }
6
+
7
+ interface ImportMeta {
8
+ readonly env: ImportMetaEnv;
9
+ }
@@ -0,0 +1,43 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ darkMode: ['class'],
4
+ content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
5
+ theme: {
6
+ extend: {
7
+ colors: {
8
+ border: 'hsl(var(--border))',
9
+ input: 'hsl(var(--input))',
10
+ ring: 'hsl(var(--ring))',
11
+ background: 'hsl(var(--background))',
12
+ foreground: 'hsl(var(--foreground))',
13
+ primary: {
14
+ DEFAULT: 'hsl(var(--primary))',
15
+ foreground: 'hsl(var(--primary-foreground))'
16
+ },
17
+ secondary: {
18
+ DEFAULT: 'hsl(var(--secondary))',
19
+ foreground: 'hsl(var(--secondary-foreground))'
20
+ },
21
+ muted: {
22
+ DEFAULT: 'hsl(var(--muted))',
23
+ foreground: 'hsl(var(--muted-foreground))'
24
+ },
25
+ accent: {
26
+ DEFAULT: 'hsl(var(--accent))',
27
+ foreground: 'hsl(var(--accent-foreground))'
28
+ },
29
+ destructive: {
30
+ DEFAULT: 'hsl(var(--destructive))',
31
+ foreground: 'hsl(var(--destructive-foreground))'
32
+ }
33
+ },
34
+ borderRadius: {
35
+ lg: 'var(--radius)',
36
+ md: 'calc(var(--radius) - 2px)',
37
+ sm: 'calc(var(--radius) - 4px)'
38
+ }
39
+ }
40
+ },
41
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
42
+ plugins: [require('tailwindcss-animate')]
43
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "target": "ES2020",
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "moduleResolution": "bundler",
8
+ "jsx": "react-jsx",
9
+ "noEmit": true,
10
+ "allowImportingTsExtensions": true,
11
+ "types": ["vite/client"],
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "@/*": ["./src/*"]
15
+ }
16
+ },
17
+ "include": ["src"]
18
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+ import path from 'path';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ '@': path.resolve(__dirname, './src')
10
+ }
11
+ },
12
+ server: {
13
+ port: 5173,
14
+ proxy: {
15
+ '/api': {
16
+ target: 'http://127.0.0.1:18791',
17
+ changeOrigin: true
18
+ },
19
+ '/ws': {
20
+ target: 'ws://127.0.0.1:18791',
21
+ ws: true
22
+ }
23
+ }
24
+ }
25
+ });