@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
|
@@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
9
9
|
<input
|
|
10
10
|
type={type}
|
|
11
11
|
className={cn(
|
|
12
|
-
'flex h-10 w-full rounded-
|
|
12
|
+
'flex h-10 w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-400 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50',
|
|
13
13
|
className
|
|
14
14
|
)}
|
|
15
15
|
ref={ref}
|
|
@@ -8,7 +8,7 @@ const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
|
|
8
8
|
<label
|
|
9
9
|
ref={ref}
|
|
10
10
|
className={cn(
|
|
11
|
-
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
|
11
|
+
'text-sm font-medium leading-none text-gray-700 peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
|
|
12
12
|
className
|
|
13
13
|
)}
|
|
14
14
|
{...props}
|
|
@@ -15,8 +15,8 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
|
|
|
15
15
|
aria-checked={checked}
|
|
16
16
|
ref={ref}
|
|
17
17
|
className={cn(
|
|
18
|
-
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-
|
|
19
|
-
checked ? 'bg-primary' : 'bg-
|
|
18
|
+
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors duration-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50',
|
|
19
|
+
checked ? 'bg-primary' : 'bg-gray-200 hover:bg-gray-300',
|
|
20
20
|
className
|
|
21
21
|
)}
|
|
22
22
|
onClick={() => onCheckedChange?.(!checked)}
|
|
@@ -24,7 +24,7 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
|
|
|
24
24
|
>
|
|
25
25
|
<span
|
|
26
26
|
className={cn(
|
|
27
|
-
'pointer-events-none block h-5 w-5 rounded-full bg-
|
|
27
|
+
'pointer-events-none block h-5 w-5 rounded-full bg-white shadow-md ring-0 transition-transform duration-fast',
|
|
28
28
|
checked ? 'translate-x-5' : 'translate-x-0'
|
|
29
29
|
)}
|
|
30
30
|
/>
|
|
@@ -16,7 +16,7 @@ interface TabsProps {
|
|
|
16
16
|
|
|
17
17
|
export function Tabs({ tabs, activeTab, onChange, className }: TabsProps) {
|
|
18
18
|
return (
|
|
19
|
-
<div className={cn('flex items-center gap-8 border-b border-
|
|
19
|
+
<div className={cn('flex items-center gap-8 border-b border-gray-200 mb-8', className)}>
|
|
20
20
|
{tabs.map((tab) => {
|
|
21
21
|
const isActive = activeTab === tab.id;
|
|
22
22
|
return (
|
|
@@ -24,18 +24,18 @@ export function Tabs({ tabs, activeTab, onChange, className }: TabsProps) {
|
|
|
24
24
|
key={tab.id}
|
|
25
25
|
onClick={() => onChange(tab.id)}
|
|
26
26
|
className={cn(
|
|
27
|
-
'relative pb-4 text-[15px] font-semibold transition-all duration-
|
|
27
|
+
'relative pb-4 text-[15px] font-semibold transition-all duration-fast flex items-center gap-2',
|
|
28
28
|
isActive
|
|
29
|
-
? 'text-
|
|
30
|
-
: 'text-
|
|
29
|
+
? 'text-primary'
|
|
30
|
+
: 'text-gray-500 hover:text-gray-700'
|
|
31
31
|
)}
|
|
32
32
|
>
|
|
33
33
|
{tab.label}
|
|
34
34
|
{tab.count !== undefined && (
|
|
35
|
-
<span className="text-[11px] font-medium text-
|
|
35
|
+
<span className="text-[11px] font-medium text-gray-400">{tab.count.toLocaleString()}</span>
|
|
36
36
|
)}
|
|
37
37
|
{isActive && (
|
|
38
|
-
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-
|
|
38
|
+
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary animate-in fade-in slide-in-from-left-2 duration-300" />
|
|
39
39
|
)}
|
|
40
40
|
</button>
|
|
41
41
|
);
|
|
@@ -42,7 +42,7 @@ export function TabsList({ children, className }: TabsListProps) {
|
|
|
42
42
|
return (
|
|
43
43
|
<div
|
|
44
44
|
className={cn(
|
|
45
|
-
'inline-flex h-10 items-center justify-center rounded-
|
|
45
|
+
'inline-flex h-10 items-center justify-center rounded-lg bg-gray-100 p-1 text-gray-600',
|
|
46
46
|
className
|
|
47
47
|
)}
|
|
48
48
|
>
|
|
@@ -62,10 +62,10 @@ export function TabsTrigger({ value, children, className }: TabsTriggerProps) {
|
|
|
62
62
|
type="button"
|
|
63
63
|
onClick={() => context.onValueChange(value)}
|
|
64
64
|
className={cn(
|
|
65
|
-
'inline-flex items-center justify-center whitespace-nowrap rounded-
|
|
65
|
+
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium ring-offset-white transition-all duration-fast focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
66
66
|
isActive
|
|
67
|
-
? 'bg-
|
|
68
|
-
: 'hover:bg-
|
|
67
|
+
? 'bg-white text-gray-900 shadow-sm'
|
|
68
|
+
: 'hover:bg-white/50 hover:text-gray-700',
|
|
69
69
|
className
|
|
70
70
|
)}
|
|
71
71
|
>
|
|
@@ -78,10 +78,11 @@ export function TabsContent({ value, children, className }: TabsContentProps) {
|
|
|
78
78
|
const context = React.useContext(TabsContext);
|
|
79
79
|
if (!context) throw new Error('TabsContent must be used within Tabs');
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
const isActive = context.value === value;
|
|
82
|
+
if (!isActive) return null;
|
|
82
83
|
|
|
83
84
|
return (
|
|
84
|
-
<div className={cn('mt-2
|
|
85
|
+
<div className={cn('mt-2 animate-fade-in', className)}>
|
|
85
86
|
{children}
|
|
86
87
|
</div>
|
|
87
88
|
);
|
package/src/hooks/useConfig.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
2
|
-
import { fetchConfig, fetchConfigMeta, updateModel, updateProvider, updateChannel
|
|
2
|
+
import { fetchConfig, fetchConfigMeta, updateModel, updateProvider, updateChannel } from '@/api/config';
|
|
3
3
|
import { toast } from 'sonner';
|
|
4
4
|
import { t } from '@/lib/i18n';
|
|
5
5
|
|
|
@@ -59,37 +59,10 @@ export function useUpdateChannel() {
|
|
|
59
59
|
updateChannel(channel, data as Parameters<typeof updateChannel>[1]),
|
|
60
60
|
onSuccess: () => {
|
|
61
61
|
queryClient.invalidateQueries({ queryKey: ['config'] });
|
|
62
|
-
toast.success(t('
|
|
63
|
-
},
|
|
64
|
-
onError: (error: Error) => {
|
|
65
|
-
toast.error(t('configSaveFailed') + ': ' + error.message);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function useUpdateUiConfig() {
|
|
71
|
-
const queryClient = useQueryClient();
|
|
72
|
-
|
|
73
|
-
return useMutation({
|
|
74
|
-
mutationFn: updateUiConfig,
|
|
75
|
-
onSuccess: () => {
|
|
76
|
-
queryClient.invalidateQueries({ queryKey: ['config'] });
|
|
77
|
-
toast.success(t('configSaved'));
|
|
62
|
+
toast.success(t('configSavedApplied'));
|
|
78
63
|
},
|
|
79
64
|
onError: (error: Error) => {
|
|
80
65
|
toast.error(t('configSaveFailed') + ': ' + error.message);
|
|
81
66
|
}
|
|
82
67
|
});
|
|
83
68
|
}
|
|
84
|
-
|
|
85
|
-
export function useReloadConfig() {
|
|
86
|
-
return useMutation({
|
|
87
|
-
mutationFn: reloadConfig,
|
|
88
|
-
onSuccess: () => {
|
|
89
|
-
toast.success(t('configReloaded'));
|
|
90
|
-
},
|
|
91
|
-
onError: (error: Error) => {
|
|
92
|
-
toast.error(t('configReloadFailed') + ': ' + error.message);
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}
|
package/src/index.css
CHANGED
|
@@ -1,55 +1,67 @@
|
|
|
1
|
+
/* Import Design System - must be first */
|
|
2
|
+
@import './styles/design-system.css';
|
|
3
|
+
|
|
1
4
|
@tailwind base;
|
|
2
5
|
@tailwind components;
|
|
3
6
|
@tailwind utilities;
|
|
4
7
|
|
|
5
8
|
@layer base {
|
|
6
9
|
:root {
|
|
7
|
-
/*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
--
|
|
11
|
-
|
|
10
|
+
/* ========================================
|
|
11
|
+
CORE VARIABLES (Mapped to Design System)
|
|
12
|
+
======================================== */
|
|
13
|
+
--background: 210 20% 98%;
|
|
14
|
+
--foreground: 221 39% 11%;
|
|
15
|
+
|
|
12
16
|
--card: 0 0% 100%;
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
--card-foreground: 221 39% 11%;
|
|
18
|
+
|
|
15
19
|
--popover: 0 0% 100%;
|
|
16
|
-
--popover-foreground:
|
|
17
|
-
|
|
18
|
-
/*
|
|
20
|
+
--popover-foreground: 221 39% 11%;
|
|
21
|
+
|
|
22
|
+
/* Primary: Brand Blue */
|
|
23
|
+
--primary: 217 80% 55%;
|
|
19
24
|
--primary-foreground: 0 0% 100%;
|
|
20
|
-
|
|
21
|
-
/*
|
|
22
|
-
--secondary
|
|
23
|
-
--
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
--
|
|
25
|
+
|
|
26
|
+
/* Secondary: Light Gray */
|
|
27
|
+
--secondary: 220 14% 96%;
|
|
28
|
+
--secondary-foreground: 215 28% 17%;
|
|
29
|
+
|
|
30
|
+
/* Muted */
|
|
31
|
+
--muted: 220 14% 96%;
|
|
32
|
+
--muted-foreground: 220 9% 46%;
|
|
33
|
+
|
|
34
|
+
/* Accent */
|
|
35
|
+
--accent: 217 100% 97%;
|
|
36
|
+
--accent-foreground: 217 70% 40%;
|
|
37
|
+
|
|
38
|
+
/* Destructive */
|
|
28
39
|
--destructive: 0 84% 60%;
|
|
29
40
|
--destructive-foreground: 0 0% 98%;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
--
|
|
33
|
-
--
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
--milk-
|
|
39
|
-
--milk-
|
|
40
|
-
--milk-
|
|
41
|
-
--milk-
|
|
42
|
-
--milk-
|
|
43
|
-
--milk-
|
|
44
|
-
--milk-
|
|
45
|
-
--milk-
|
|
46
|
-
--milk-
|
|
41
|
+
|
|
42
|
+
/* UI Elements */
|
|
43
|
+
--border: 220 13% 91%;
|
|
44
|
+
--input: 220 13% 91%;
|
|
45
|
+
--ring: 217 80% 55%;
|
|
46
|
+
--radius: 0.75rem;
|
|
47
|
+
|
|
48
|
+
/* Legacy compatibility */
|
|
49
|
+
--milk-50: 210 20% 98%;
|
|
50
|
+
--milk-100: 220 14% 96%;
|
|
51
|
+
--milk-200: 220 13% 91%;
|
|
52
|
+
--milk-300: 216 12% 84%;
|
|
53
|
+
--milk-400: 218 11% 65%;
|
|
54
|
+
--milk-500: 220 9% 46%;
|
|
55
|
+
--milk-600: 215 14% 34%;
|
|
56
|
+
--milk-700: 217 19% 27%;
|
|
57
|
+
--milk-800: 215 28% 17%;
|
|
58
|
+
--milk-900: 221 39% 11%;
|
|
47
59
|
}
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
@layer base {
|
|
51
63
|
* {
|
|
52
|
-
@apply border-border
|
|
64
|
+
@apply border-border;
|
|
53
65
|
}
|
|
54
66
|
|
|
55
67
|
html {
|
|
@@ -60,20 +72,21 @@
|
|
|
60
72
|
|
|
61
73
|
body {
|
|
62
74
|
@apply bg-background text-foreground;
|
|
63
|
-
font-family: -
|
|
75
|
+
font-family: var(--font-sans);
|
|
64
76
|
overflow: hidden;
|
|
65
77
|
}
|
|
66
78
|
|
|
67
|
-
/* Smooth scrolling
|
|
79
|
+
/* Smooth scrolling */
|
|
68
80
|
* {
|
|
69
81
|
scrollbar-width: thin;
|
|
70
|
-
scrollbar-color: hsl(var(--
|
|
82
|
+
scrollbar-color: hsl(var(--gray-300)) transparent;
|
|
71
83
|
}
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
@layer utilities {
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
/* ========================================
|
|
88
|
+
SCROLLBAR
|
|
89
|
+
======================================== */
|
|
77
90
|
.custom-scrollbar::-webkit-scrollbar {
|
|
78
91
|
width: 6px;
|
|
79
92
|
height: 6px;
|
|
@@ -84,24 +97,42 @@
|
|
|
84
97
|
}
|
|
85
98
|
|
|
86
99
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
87
|
-
background: hsl(var(--
|
|
100
|
+
background: hsl(var(--gray-300));
|
|
88
101
|
border-radius: 6px;
|
|
89
102
|
}
|
|
90
103
|
|
|
91
104
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
92
|
-
background: hsl(var(--
|
|
105
|
+
background: hsl(var(--gray-400));
|
|
93
106
|
}
|
|
94
107
|
|
|
95
|
-
/*
|
|
108
|
+
/* ========================================
|
|
109
|
+
GLASSMORPHISM
|
|
110
|
+
======================================== */
|
|
96
111
|
.glass {
|
|
97
|
-
|
|
112
|
+
background: rgba(255, 255, 255, 0.8);
|
|
113
|
+
backdrop-filter: blur(12px);
|
|
114
|
+
-webkit-backdrop-filter: blur(12px);
|
|
115
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
98
116
|
}
|
|
99
117
|
|
|
100
118
|
.glass-dark {
|
|
101
|
-
|
|
119
|
+
background: rgba(0, 0, 0, 0.1);
|
|
120
|
+
backdrop-filter: blur(12px);
|
|
121
|
+
-webkit-backdrop-filter: blur(12px);
|
|
122
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* ========================================
|
|
126
|
+
SHADOWS
|
|
127
|
+
======================================== */
|
|
128
|
+
.shadow-card {
|
|
129
|
+
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.05), 0 1px 2px -1px rgb(0 0 0 / 0.05);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.shadow-card-hover {
|
|
133
|
+
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.08), 0 4px 6px -4px rgb(0 0 0 / 0.05);
|
|
102
134
|
}
|
|
103
135
|
|
|
104
|
-
/* Premium Shadows */
|
|
105
136
|
.shadow-premium {
|
|
106
137
|
box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.08), 0 4px 10px -4px rgba(0, 0, 0, 0.04);
|
|
107
138
|
}
|
|
@@ -109,15 +140,36 @@
|
|
|
109
140
|
.shadow-premium-hover {
|
|
110
141
|
box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.12), 0 8px 15px -6px rgba(0, 0, 0, 0.06);
|
|
111
142
|
}
|
|
143
|
+
|
|
144
|
+
/* ========================================
|
|
145
|
+
GRADIENTS
|
|
146
|
+
======================================== */
|
|
147
|
+
.bg-gradient-hero {
|
|
148
|
+
background: linear-gradient(180deg, hsl(var(--background)) 0%, hsl(var(--gray-50)) 100%);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.bg-gradient-subtle {
|
|
152
|
+
background: linear-gradient(180deg, hsl(var(--gray-50)) 0%, hsl(var(--background)) 100%);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* ========================================
|
|
156
|
+
FOCUS STATES
|
|
157
|
+
======================================== */
|
|
158
|
+
.focus-ring {
|
|
159
|
+
@apply focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2;
|
|
160
|
+
--tw-ring-color: hsl(var(--ring));
|
|
161
|
+
--tw-ring-offset-color: hsl(var(--ring-offset));
|
|
162
|
+
}
|
|
112
163
|
}
|
|
113
164
|
|
|
114
|
-
/*
|
|
165
|
+
/* ========================================
|
|
166
|
+
ANIMATIONS
|
|
167
|
+
======================================== */
|
|
115
168
|
@keyframes fadeIn {
|
|
116
169
|
from {
|
|
117
170
|
opacity: 0;
|
|
118
171
|
transform: translateY(12px);
|
|
119
172
|
}
|
|
120
|
-
|
|
121
173
|
to {
|
|
122
174
|
opacity: 1;
|
|
123
175
|
transform: translateY(0);
|
|
@@ -129,7 +181,6 @@
|
|
|
129
181
|
opacity: 0;
|
|
130
182
|
transform: translateX(-12px);
|
|
131
183
|
}
|
|
132
|
-
|
|
133
184
|
to {
|
|
134
185
|
opacity: 1;
|
|
135
186
|
transform: translateX(0);
|
|
@@ -141,7 +192,6 @@
|
|
|
141
192
|
opacity: 0;
|
|
142
193
|
transform: scale(0.97);
|
|
143
194
|
}
|
|
144
|
-
|
|
145
195
|
to {
|
|
146
196
|
opacity: 1;
|
|
147
197
|
transform: scale(1);
|
|
@@ -149,12 +199,9 @@
|
|
|
149
199
|
}
|
|
150
200
|
|
|
151
201
|
@keyframes pulse-soft {
|
|
152
|
-
|
|
153
|
-
0%,
|
|
154
|
-
100% {
|
|
202
|
+
0%, 100% {
|
|
155
203
|
opacity: 1;
|
|
156
204
|
}
|
|
157
|
-
|
|
158
205
|
50% {
|
|
159
206
|
opacity: 0.8;
|
|
160
207
|
}
|
|
@@ -174,4 +221,4 @@
|
|
|
174
221
|
|
|
175
222
|
.animate-pulse-soft {
|
|
176
223
|
animation: pulse-soft 3s ease-in-out infinite;
|
|
177
|
-
}
|
|
224
|
+
}
|
package/src/lib/i18n.ts
CHANGED
|
@@ -4,7 +4,6 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
4
4
|
model: { zh: '模型', en: 'Model' },
|
|
5
5
|
providers: { zh: '提供商', en: 'Providers' },
|
|
6
6
|
channels: { zh: '渠道', en: 'Channels' },
|
|
7
|
-
uiConfig: { zh: '界面', en: 'UI' },
|
|
8
7
|
|
|
9
8
|
// Common
|
|
10
9
|
enabled: { zh: '启用', en: 'Enabled' },
|
|
@@ -29,6 +28,10 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
29
28
|
apiKey: { zh: 'API 密钥', en: 'API Key' },
|
|
30
29
|
apiBase: { zh: 'API Base', en: 'API Base' },
|
|
31
30
|
extraHeaders: { zh: '额外请求头', en: 'Extra Headers' },
|
|
31
|
+
wireApi: { zh: '请求接口', en: 'Wire API' },
|
|
32
|
+
wireApiAuto: { zh: '自动(优先 Chat,必要时 Responses)', en: 'Auto (Chat with fallback)' },
|
|
33
|
+
wireApiChat: { zh: 'Chat Completions', en: 'Chat Completions' },
|
|
34
|
+
wireApiResponses: { zh: 'Responses', en: 'Responses' },
|
|
32
35
|
apiKeySet: { zh: '已设置', en: 'Set' },
|
|
33
36
|
apiKeyNotSet: { zh: '未设置', en: 'Not Set' },
|
|
34
37
|
showKey: { zh: '显示密钥', en: 'Show Key' },
|
|
@@ -41,6 +44,7 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
41
44
|
appToken: { zh: 'App Token', en: 'App Token' },
|
|
42
45
|
appId: { zh: 'App ID', en: 'App ID' },
|
|
43
46
|
appSecret: { zh: 'App Secret', en: 'App Secret' },
|
|
47
|
+
markdownSupport: { zh: 'Markdown 支持', en: 'Markdown Support' },
|
|
44
48
|
clientId: { zh: 'Client ID', en: 'Client ID' },
|
|
45
49
|
clientSecret: { zh: 'Client Secret', en: 'Client Secret' },
|
|
46
50
|
encryptKey: { zh: '加密密钥', en: 'Encrypt Key' },
|
|
@@ -93,11 +97,7 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
93
97
|
replyDelayMs: { zh: '回复延迟(ms)', en: 'Reply Delay (ms)' },
|
|
94
98
|
secret: { zh: '密钥', en: 'Secret' },
|
|
95
99
|
|
|
96
|
-
// UI
|
|
97
|
-
host: { zh: '主机', en: 'Host' },
|
|
98
|
-
port: { zh: '端口', en: 'Port' },
|
|
99
|
-
open: { zh: '自动打开', en: 'Open Automatically' },
|
|
100
|
-
reloadConfig: { zh: '重载配置', en: 'Reload Config' },
|
|
100
|
+
// UI
|
|
101
101
|
saveVerifyConnect: { zh: '保存并验证 / 连接', en: 'Save & Verify / Connect' },
|
|
102
102
|
|
|
103
103
|
// Status
|
|
@@ -108,6 +108,7 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
108
108
|
|
|
109
109
|
// Messages
|
|
110
110
|
configSaved: { zh: '配置已保存', en: 'Configuration saved' },
|
|
111
|
+
configSavedApplied: { zh: '配置已保存并已应用', en: 'Configuration saved and applied' },
|
|
111
112
|
configSaveFailed: { zh: '保存配置失败', en: 'Failed to save configuration' },
|
|
112
113
|
configReloaded: { zh: '配置已重载', en: 'Configuration reloaded' },
|
|
113
114
|
configReloadFailed: { zh: '重载配置失败', en: 'Failed to reload configuration' },
|
package/src/lib/logos.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
type LogoMap = Record<string, string>;
|
|
2
|
+
|
|
3
|
+
const PROVIDER_LOGOS: LogoMap = {
|
|
4
|
+
openrouter: "openrouter.svg",
|
|
5
|
+
aihubmix: "aihubmix.png",
|
|
6
|
+
anthropic: "anthropic.svg",
|
|
7
|
+
openai: "openai.svg",
|
|
8
|
+
gemini: "gemini.svg",
|
|
9
|
+
deepseek: "deepseek.png",
|
|
10
|
+
zhipu: "zhipu.svg",
|
|
11
|
+
dashscope: "dashscope.png",
|
|
12
|
+
moonshot: "moonshot.png",
|
|
13
|
+
minimax: "minimax.svg",
|
|
14
|
+
vllm: "vllm.svg",
|
|
15
|
+
groq: "groq.svg"
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const CHANNEL_LOGOS: LogoMap = {
|
|
19
|
+
telegram: "telegram.svg",
|
|
20
|
+
slack: "slack.svg",
|
|
21
|
+
discord: "discord.svg",
|
|
22
|
+
whatsapp: "whatsapp.svg",
|
|
23
|
+
qq: "qq.svg",
|
|
24
|
+
feishu: "feishu.svg",
|
|
25
|
+
dingtalk: "dingtalk.svg",
|
|
26
|
+
mochat: "mochat.svg",
|
|
27
|
+
email: "email.svg"
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function resolveLogo(map: LogoMap, name: string): string | null {
|
|
31
|
+
const key = name.toLowerCase();
|
|
32
|
+
const file = map[key];
|
|
33
|
+
return file ? `/logos/${file}` : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getProviderLogo(name: string): string | null {
|
|
37
|
+
return resolveLogo(PROVIDER_LOGOS, name);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getChannelLogo(name: string): string | null {
|
|
41
|
+
return resolveLogo(CHANNEL_LOGOS, name);
|
|
42
|
+
}
|
package/src/stores/ui.store.ts
CHANGED
|
@@ -4,7 +4,7 @@ type ConnectionStatus = 'connected' | 'disconnected' | 'connecting';
|
|
|
4
4
|
|
|
5
5
|
interface UiState {
|
|
6
6
|
// Active configuration tab
|
|
7
|
-
activeTab: 'model' | 'providers' | 'channels'
|
|
7
|
+
activeTab: 'model' | 'providers' | 'channels';
|
|
8
8
|
setActiveTab: (tab: UiState['activeTab']) => void;
|
|
9
9
|
|
|
10
10
|
// Connection status
|