@nextclaw/ui 0.5.48 → 0.6.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.
- package/CHANGELOG.md +18 -0
- package/dist/assets/ChannelsList-CkCpHSto.js +1 -0
- package/dist/assets/ChatPage-DM4XNsrW.js +32 -0
- package/dist/assets/DocBrowser-B5Aqiz6W.js +1 -0
- package/dist/assets/MarketplacePage-BIi0bBdW.js +49 -0
- package/dist/assets/ModelConfig-BTFiEAxQ.js +1 -0
- package/dist/assets/{ProvidersList-BXHpjVtO.js → ProvidersList-cdk1d-G_.js} +1 -1
- package/dist/assets/RuntimeConfig-CFqFsXmR.js +1 -0
- package/dist/assets/{SecretsConfig-KkgMzdt1.js → SecretsConfig-CIKasCek.js} +2 -2
- package/dist/assets/SessionsConfig-mnCLFtbo.js +2 -0
- package/dist/assets/{card-D7NY0Szf.js → card-C1BUfR85.js} +1 -1
- package/dist/assets/index-Dxas8MJ9.js +2 -0
- package/dist/assets/index-P4YzN9iS.css +1 -0
- package/dist/assets/{label-Ojs7Al6B.js → label-CwWfYbuj.js} +1 -1
- package/dist/assets/{logos-B1qBsCSi.js → logos-DDyjHSEU.js} +1 -1
- package/dist/assets/{page-layout-CUMMO0nN.js → page-layout-DKTRKcHL.js} +1 -1
- package/dist/assets/provider-models-y4mUDcGF.js +1 -0
- package/dist/assets/{switch-BdhS_16-.js → switch-Bi3yeYiC.js} +1 -1
- package/dist/assets/{tabs-custom-D261E5EA.js → tabs-custom-HZFNZrc0.js} +1 -1
- package/dist/assets/useConfig-CgzVQTZl.js +6 -0
- package/dist/assets/{useConfirmDialog-BUKGHDL6.js → useConfirmDialog-DwD21HlD.js} +2 -2
- package/dist/assets/{vendor-Dh04PGww.js → vendor-Ylg6Wdt_.js} +84 -69
- package/dist/index.html +3 -3
- package/package.json +2 -1
- package/src/App.tsx +10 -6
- package/src/api/config.ts +42 -1
- package/src/api/types.ts +29 -0
- package/src/components/chat/ChatConversationPanel.tsx +109 -85
- package/src/components/chat/ChatInputBar.tsx +245 -0
- package/src/components/chat/ChatPage.tsx +365 -187
- package/src/components/chat/ChatSidebar.tsx +242 -0
- package/src/components/chat/ChatThread.tsx +92 -25
- package/src/components/chat/ChatWelcome.tsx +61 -0
- package/src/components/chat/SkillsPicker.tsx +137 -0
- package/src/components/chat/useChatStreamController.ts +287 -56
- package/src/components/config/ChannelForm.tsx +1 -1
- package/src/components/config/ChannelsList.tsx +3 -3
- package/src/components/config/ModelConfig.tsx +11 -89
- package/src/components/config/RuntimeConfig.tsx +29 -1
- package/src/components/layout/AppLayout.tsx +42 -6
- package/src/components/layout/Sidebar.tsx +68 -62
- package/src/components/marketplace/MarketplacePage.tsx +13 -3
- package/src/components/ui/popover.tsx +31 -0
- package/src/hooks/useConfig.ts +18 -0
- package/src/lib/i18n.ts +53 -0
- package/src/lib/provider-models.ts +129 -0
- package/dist/assets/ChannelsList-C8cguFLc.js +0 -1
- package/dist/assets/ChatPage-BkHWNUNR.js +0 -32
- package/dist/assets/CronConfig-D-ESQlvk.js +0 -1
- package/dist/assets/DocBrowser-B9ZD6pAk.js +0 -1
- package/dist/assets/MarketplacePage-Ds_l9KTF.js +0 -49
- package/dist/assets/ModelConfig-N1tbLv9b.js +0 -1
- package/dist/assets/RuntimeConfig-KsKfkjgv.js +0 -1
- package/dist/assets/SessionsConfig-CWBp8IPf.js +0 -2
- package/dist/assets/index-BRBYYgR_.js +0 -2
- package/dist/assets/index-C5cdRzpO.css +0 -1
- package/dist/assets/useConfig-txxbxXnT.js +0 -6
|
@@ -28,6 +28,7 @@ function createEmptyAgent(): AgentProfileView {
|
|
|
28
28
|
default: false,
|
|
29
29
|
workspace: '',
|
|
30
30
|
model: '',
|
|
31
|
+
engine: '',
|
|
31
32
|
contextTokens: undefined,
|
|
32
33
|
maxToolIterations: undefined
|
|
33
34
|
};
|
|
@@ -62,6 +63,7 @@ export function RuntimeConfig() {
|
|
|
62
63
|
const [dmScope, setDmScope] = useState<DmScope>('per-channel-peer');
|
|
63
64
|
const [maxPingPongTurns, setMaxPingPongTurns] = useState(0);
|
|
64
65
|
const [defaultContextTokens, setDefaultContextTokens] = useState(200000);
|
|
66
|
+
const [defaultEngine, setDefaultEngine] = useState('native');
|
|
65
67
|
|
|
66
68
|
useEffect(() => {
|
|
67
69
|
if (!config) {
|
|
@@ -73,6 +75,7 @@ export function RuntimeConfig() {
|
|
|
73
75
|
default: Boolean(agent.default),
|
|
74
76
|
workspace: agent.workspace ?? '',
|
|
75
77
|
model: agent.model ?? '',
|
|
78
|
+
engine: agent.engine ?? '',
|
|
76
79
|
contextTokens: agent.contextTokens,
|
|
77
80
|
maxToolIterations: agent.maxToolIterations
|
|
78
81
|
}))
|
|
@@ -95,13 +98,16 @@ export function RuntimeConfig() {
|
|
|
95
98
|
setDmScope((config.session?.dmScope as DmScope) ?? 'per-channel-peer');
|
|
96
99
|
setMaxPingPongTurns(config.session?.agentToAgent?.maxPingPongTurns ?? 0);
|
|
97
100
|
setDefaultContextTokens(config.agents.defaults.contextTokens ?? 200000);
|
|
101
|
+
setDefaultEngine(config.agents.defaults.engine ?? 'native');
|
|
98
102
|
}, [config]);
|
|
99
103
|
|
|
100
104
|
const uiHints = schema?.uiHints;
|
|
101
105
|
const dmScopeHint = hintForPath('session.dmScope', uiHints);
|
|
102
106
|
const maxPingHint = hintForPath('session.agentToAgent.maxPingPongTurns', uiHints);
|
|
103
107
|
const defaultContextTokensHint = hintForPath('agents.defaults.contextTokens', uiHints);
|
|
108
|
+
const defaultEngineHint = hintForPath('agents.defaults.engine', uiHints);
|
|
104
109
|
const agentContextTokensHint = hintForPath('agents.list.*.contextTokens', uiHints);
|
|
110
|
+
const agentEngineHint = hintForPath('agents.list.*.engine', uiHints);
|
|
105
111
|
const agentsHint = hintForPath('agents.list', uiHints);
|
|
106
112
|
const bindingsHint = hintForPath('bindings', uiHints);
|
|
107
113
|
|
|
@@ -141,6 +147,9 @@ export function RuntimeConfig() {
|
|
|
141
147
|
if (agent.model?.trim()) {
|
|
142
148
|
normalized.model = agent.model.trim();
|
|
143
149
|
}
|
|
150
|
+
if (agent.engine?.trim()) {
|
|
151
|
+
normalized.engine = agent.engine.trim();
|
|
152
|
+
}
|
|
144
153
|
if (typeof agent.contextTokens === 'number') {
|
|
145
154
|
normalized.contextTokens = Math.max(1000, agent.contextTokens);
|
|
146
155
|
}
|
|
@@ -203,7 +212,8 @@ export function RuntimeConfig() {
|
|
|
203
212
|
data: {
|
|
204
213
|
agents: {
|
|
205
214
|
defaults: {
|
|
206
|
-
contextTokens: Math.max(1000, defaultContextTokens)
|
|
215
|
+
contextTokens: Math.max(1000, defaultContextTokens),
|
|
216
|
+
engine: defaultEngine.trim() || 'native'
|
|
207
217
|
},
|
|
208
218
|
list: normalizedAgents
|
|
209
219
|
},
|
|
@@ -251,6 +261,19 @@ export function RuntimeConfig() {
|
|
|
251
261
|
{defaultContextTokensHint?.help ?? t('defaultContextTokensHelp')}
|
|
252
262
|
</p>
|
|
253
263
|
</div>
|
|
264
|
+
<div className="space-y-2">
|
|
265
|
+
<label className="text-sm font-medium text-gray-800">
|
|
266
|
+
{defaultEngineHint?.label ?? t('defaultEngine')}
|
|
267
|
+
</label>
|
|
268
|
+
<Input
|
|
269
|
+
value={defaultEngine}
|
|
270
|
+
onChange={(event) => setDefaultEngine(event.target.value)}
|
|
271
|
+
placeholder={t('defaultEnginePlaceholder')}
|
|
272
|
+
/>
|
|
273
|
+
<p className="text-xs text-gray-500">
|
|
274
|
+
{defaultEngineHint?.help ?? t('defaultEngineHelp')}
|
|
275
|
+
</p>
|
|
276
|
+
</div>
|
|
254
277
|
<div className="space-y-2">
|
|
255
278
|
<label className="text-sm font-medium text-gray-800">{dmScopeHint?.label ?? t('dmScope')}</label>
|
|
256
279
|
<Select value={dmScope} onValueChange={(v) => setDmScope(v as DmScope)}>
|
|
@@ -308,6 +331,11 @@ export function RuntimeConfig() {
|
|
|
308
331
|
onChange={(event) => updateAgent(index, { model: event.target.value })}
|
|
309
332
|
placeholder={t('modelOverridePlaceholder')}
|
|
310
333
|
/>
|
|
334
|
+
<Input
|
|
335
|
+
value={agent.engine ?? ''}
|
|
336
|
+
onChange={(event) => updateAgent(index, { engine: event.target.value })}
|
|
337
|
+
placeholder={agentEngineHint?.label ?? t('engineOverridePlaceholder')}
|
|
338
|
+
/>
|
|
311
339
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
|
312
340
|
<Input
|
|
313
341
|
type="number"
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { lazy, Suspense } from 'react';
|
|
2
|
+
import { useLocation } from 'react-router-dom';
|
|
2
3
|
import { Sidebar } from './Sidebar';
|
|
3
4
|
import { DocBrowserProvider, useDocBrowser } from '@/components/doc-browser/DocBrowserContext';
|
|
4
5
|
import { useDocLinkInterceptor } from '@/components/doc-browser/useDocLinkInterceptor';
|
|
6
|
+
import { cn } from '@/lib/utils';
|
|
5
7
|
|
|
6
8
|
const DocBrowser = lazy(async () => ({ default: (await import('@/components/doc-browser/DocBrowser')).DocBrowser }));
|
|
7
9
|
|
|
@@ -9,20 +11,54 @@ interface AppLayoutProps {
|
|
|
9
11
|
children: React.ReactNode;
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
function isMainWorkspaceRoute(pathname: string): boolean {
|
|
15
|
+
const normalized = pathname.toLowerCase();
|
|
16
|
+
return (
|
|
17
|
+
normalized === '/chat' ||
|
|
18
|
+
normalized.startsWith('/chat/') ||
|
|
19
|
+
normalized === '/skills' ||
|
|
20
|
+
normalized.startsWith('/skills/') ||
|
|
21
|
+
normalized === '/cron' ||
|
|
22
|
+
normalized.startsWith('/cron/')
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isChannelsRoute(pathname: string): boolean {
|
|
27
|
+
const normalized = pathname.toLowerCase();
|
|
28
|
+
return normalized === '/channels' || normalized.startsWith('/channels/');
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
function AppLayoutInner({ children }: AppLayoutProps) {
|
|
13
32
|
const { isOpen, mode } = useDocBrowser();
|
|
14
33
|
useDocLinkInterceptor();
|
|
34
|
+
const { pathname } = useLocation();
|
|
35
|
+
const isMainRoute = isMainWorkspaceRoute(pathname);
|
|
36
|
+
const lockPageScroll = isChannelsRoute(pathname);
|
|
15
37
|
|
|
16
38
|
return (
|
|
17
39
|
<div className="h-screen flex bg-background font-sans text-foreground">
|
|
18
|
-
<Sidebar />
|
|
40
|
+
{!isMainRoute && <Sidebar mode="settings" />}
|
|
19
41
|
<div className="flex-1 flex min-w-0 overflow-hidden relative">
|
|
20
42
|
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
|
21
|
-
|
|
22
|
-
<div className="
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
43
|
+
{isMainRoute ? (
|
|
44
|
+
<div className="flex-1 h-full overflow-hidden">{children}</div>
|
|
45
|
+
) : (
|
|
46
|
+
<main
|
|
47
|
+
className={cn(
|
|
48
|
+
'flex-1 custom-scrollbar p-8',
|
|
49
|
+
lockPageScroll ? 'overflow-auto xl:overflow-hidden' : 'overflow-auto'
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
<div
|
|
53
|
+
className={cn(
|
|
54
|
+
'max-w-6xl mx-auto animate-fade-in h-full',
|
|
55
|
+
lockPageScroll && 'min-h-0 xl:overflow-hidden'
|
|
56
|
+
)}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</div>
|
|
60
|
+
</main>
|
|
61
|
+
)}
|
|
26
62
|
</div>
|
|
27
63
|
{/* Doc Browser: docked mode renders inline, floating mode renders as overlay */}
|
|
28
64
|
{isOpen && mode === 'docked' && (
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { cn } from '@/lib/utils';
|
|
2
2
|
import { LANGUAGE_OPTIONS, t, type I18nLanguage } from '@/lib/i18n';
|
|
3
3
|
import { THEME_OPTIONS, type UiTheme } from '@/lib/theme';
|
|
4
|
-
import { Cpu, GitBranch, History, MessageCircle, MessageSquare, Sparkles, BookOpen, Plug, BrainCircuit, AlarmClock, Languages, Palette, KeyRound } from 'lucide-react';
|
|
4
|
+
import { Cpu, GitBranch, History, MessageCircle, MessageSquare, Sparkles, BookOpen, Plug, BrainCircuit, AlarmClock, Languages, Palette, KeyRound, Settings, ArrowLeft } from 'lucide-react';
|
|
5
5
|
import { NavLink } from 'react-router-dom';
|
|
6
6
|
import { useDocBrowser } from '@/components/doc-browser';
|
|
7
7
|
import { useI18n } from '@/components/providers/I18nProvider';
|
|
8
8
|
import { useTheme } from '@/components/providers/ThemeProvider';
|
|
9
9
|
import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select';
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
type SidebarMode = 'main' | 'settings';
|
|
12
|
+
|
|
13
|
+
type SidebarProps = {
|
|
14
|
+
mode: SidebarMode;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function Sidebar({ mode }: SidebarProps) {
|
|
12
18
|
const docBrowser = useDocBrowser();
|
|
13
19
|
const { language, setLanguage } = useI18n();
|
|
14
20
|
const { theme, setTheme } = useTheme();
|
|
@@ -31,12 +37,25 @@ export function Sidebar() {
|
|
|
31
37
|
};
|
|
32
38
|
|
|
33
39
|
// Core navigation items - primary features
|
|
34
|
-
const
|
|
40
|
+
const mainNavItems = [
|
|
35
41
|
{
|
|
36
42
|
target: '/chat',
|
|
37
43
|
label: t('chat'),
|
|
38
44
|
icon: MessageCircle,
|
|
39
45
|
},
|
|
46
|
+
{
|
|
47
|
+
target: '/chat/cron',
|
|
48
|
+
label: t('cron'),
|
|
49
|
+
icon: AlarmClock,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
target: '/chat/skills',
|
|
53
|
+
label: t('marketplaceFilterSkills'),
|
|
54
|
+
icon: BrainCircuit,
|
|
55
|
+
}
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const settingsNavItems = [
|
|
40
59
|
{
|
|
41
60
|
target: '/model',
|
|
42
61
|
label: t('model'),
|
|
@@ -52,20 +71,6 @@ export function Sidebar() {
|
|
|
52
71
|
label: t('channels'),
|
|
53
72
|
icon: MessageSquare,
|
|
54
73
|
},
|
|
55
|
-
{
|
|
56
|
-
target: '/cron',
|
|
57
|
-
label: t('cron'),
|
|
58
|
-
icon: AlarmClock,
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
target: '/marketplace/skills',
|
|
62
|
-
label: t('marketplaceFilterSkills'),
|
|
63
|
-
icon: BrainCircuit,
|
|
64
|
-
}
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
// Advanced navigation items - secondary features
|
|
68
|
-
const advancedNavItems = [
|
|
69
74
|
{
|
|
70
75
|
target: '/runtime',
|
|
71
76
|
label: t('runtime'),
|
|
@@ -87,24 +92,41 @@ export function Sidebar() {
|
|
|
87
92
|
icon: Plug,
|
|
88
93
|
}
|
|
89
94
|
];
|
|
95
|
+
const navItems = mode === 'main' ? mainNavItems : settingsNavItems;
|
|
90
96
|
|
|
91
97
|
return (
|
|
92
98
|
<aside className="w-[240px] shrink-0 flex flex-col h-full py-6 px-4 bg-secondary">
|
|
93
|
-
{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
{mode === 'settings' ? (
|
|
100
|
+
<div className="px-2 mb-6">
|
|
101
|
+
<NavLink
|
|
102
|
+
to="/chat"
|
|
103
|
+
className="group inline-flex items-center gap-2 rounded-lg border border-gray-200 bg-white px-2.5 py-1.5 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-50 hover:text-gray-900"
|
|
104
|
+
>
|
|
105
|
+
<ArrowLeft className="h-3.5 w-3.5 text-gray-500 group-hover:text-gray-800" />
|
|
106
|
+
<span>{t('backToMain')}</span>
|
|
107
|
+
</NavLink>
|
|
108
|
+
<div className="mt-5 px-1">
|
|
109
|
+
<div className="flex items-center gap-2.5">
|
|
110
|
+
<Settings className="h-5 w-5 text-gray-700" />
|
|
111
|
+
<h1 className="text-[28px] leading-none font-semibold tracking-[-0.02em] text-gray-900">{t('settings')}</h1>
|
|
112
|
+
</div>
|
|
98
113
|
</div>
|
|
99
|
-
<span className="text-[15px] font-semibold text-gray-800 tracking-[-0.01em]">NextClaw</span>
|
|
100
114
|
</div>
|
|
101
|
-
|
|
115
|
+
) : (
|
|
116
|
+
<div className="px-2 mb-8">
|
|
117
|
+
<div className="flex items-center gap-2.5 cursor-pointer">
|
|
118
|
+
<div className="h-7 w-7 rounded-lg overflow-hidden flex items-center justify-center">
|
|
119
|
+
<img src="/logo.svg" alt="NextClaw" className="h-full w-full object-contain" />
|
|
120
|
+
</div>
|
|
121
|
+
<span className="text-[15px] font-semibold text-gray-800 tracking-[-0.01em]">NextClaw</span>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
102
125
|
|
|
103
126
|
{/* Navigation */}
|
|
104
127
|
<nav className="flex-1 flex flex-col">
|
|
105
|
-
{/* Core Navigation */}
|
|
106
128
|
<ul className="space-y-1">
|
|
107
|
-
{
|
|
129
|
+
{navItems.map((item) => {
|
|
108
130
|
const Icon = item.icon;
|
|
109
131
|
|
|
110
132
|
return (
|
|
@@ -129,49 +151,33 @@ export function Sidebar() {
|
|
|
129
151
|
)}
|
|
130
152
|
</NavLink>
|
|
131
153
|
</li>
|
|
132
|
-
);
|
|
133
|
-
})}
|
|
134
|
-
</ul>
|
|
135
|
-
|
|
136
|
-
{/* Advanced Navigation */}
|
|
137
|
-
<div className="mt-3 pt-3 border-t border-[#dde0ea]">
|
|
138
|
-
<div className="px-3 mb-2">
|
|
139
|
-
<span className="text-[11px] font-medium text-gray-400 uppercase tracking-wider">{t('advanced')}</span>
|
|
140
|
-
</div>
|
|
141
|
-
<ul className="space-y-1">
|
|
142
|
-
{advancedNavItems.map((item) => {
|
|
143
|
-
const Icon = item.icon;
|
|
144
|
-
|
|
145
|
-
return (
|
|
146
|
-
<li key={item.target}>
|
|
147
|
-
<NavLink
|
|
148
|
-
to={item.target}
|
|
149
|
-
className={({ isActive }) => cn(
|
|
150
|
-
'group w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-[14px] font-medium transition-all duration-base',
|
|
151
|
-
isActive
|
|
152
|
-
? 'bg-gray-200 text-gray-900 font-semibold shadow-sm'
|
|
153
|
-
: 'text-gray-600 hover:bg-gray-200/60 hover:text-gray-900'
|
|
154
|
-
)}
|
|
155
|
-
>
|
|
156
|
-
{({ isActive }) => (
|
|
157
|
-
<>
|
|
158
|
-
<Icon className={cn(
|
|
159
|
-
'h-[17px] w-[17px] transition-colors',
|
|
160
|
-
isActive ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-800'
|
|
161
|
-
)} />
|
|
162
|
-
<span className="flex-1 text-left">{item.label}</span>
|
|
163
|
-
</>
|
|
164
|
-
)}
|
|
165
|
-
</NavLink>
|
|
166
|
-
</li>
|
|
167
154
|
);
|
|
168
155
|
})}
|
|
169
156
|
</ul>
|
|
170
|
-
</div>
|
|
171
157
|
</nav>
|
|
172
158
|
|
|
173
159
|
{/* Help Button */}
|
|
174
160
|
<div className="pt-3 border-t border-[#dde0ea] mt-3">
|
|
161
|
+
{mode === 'main' && (
|
|
162
|
+
<div className="mb-2">
|
|
163
|
+
<NavLink
|
|
164
|
+
to="/settings"
|
|
165
|
+
className={({ isActive }) => cn(
|
|
166
|
+
'group w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-[14px] font-medium transition-all duration-base',
|
|
167
|
+
isActive
|
|
168
|
+
? 'bg-gray-200 text-gray-900 font-semibold shadow-sm'
|
|
169
|
+
: 'text-gray-600 hover:bg-[#e4e7ef] hover:text-gray-900'
|
|
170
|
+
)}
|
|
171
|
+
>
|
|
172
|
+
{({ isActive }) => (
|
|
173
|
+
<>
|
|
174
|
+
<Settings className={cn('h-[17px] w-[17px] transition-colors', isActive ? 'text-gray-900' : 'text-gray-500 group-hover:text-gray-800')} />
|
|
175
|
+
<span className="flex-1 text-left">{t('settings')}</span>
|
|
176
|
+
</>
|
|
177
|
+
)}
|
|
178
|
+
</NavLink>
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
175
181
|
<div className="mb-2">
|
|
176
182
|
<Select value={theme} onValueChange={(value) => handleThemeSwitch(value as UiTheme)}>
|
|
177
183
|
<SelectTrigger className="w-full h-auto rounded-xl border-0 bg-transparent shadow-none px-3 py-2.5 text-[14px] font-medium text-gray-600 hover:bg-[#e4e7ef] focus:ring-0">
|
|
@@ -50,6 +50,9 @@ type InstalledRenderEntry = {
|
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
type MarketplaceRouteType = 'plugins' | 'skills';
|
|
53
|
+
type MarketplacePageProps = {
|
|
54
|
+
forcedType?: MarketplaceRouteType;
|
|
55
|
+
};
|
|
53
56
|
|
|
54
57
|
function normalizeMarketplaceKey(value: string | undefined): string {
|
|
55
58
|
return (value ?? '').trim().toLowerCase();
|
|
@@ -506,24 +509,31 @@ function PaginationBar(props: {
|
|
|
506
509
|
);
|
|
507
510
|
}
|
|
508
511
|
|
|
509
|
-
export function MarketplacePage() {
|
|
512
|
+
export function MarketplacePage(props: MarketplacePageProps = {}) {
|
|
510
513
|
const navigate = useNavigate();
|
|
511
514
|
const params = useParams<{ type?: string }>();
|
|
512
515
|
const { language } = useI18n();
|
|
513
516
|
const docBrowser = useDocBrowser();
|
|
517
|
+
const forcedType = props.forcedType;
|
|
514
518
|
|
|
515
519
|
const routeType: MarketplaceRouteType | null = useMemo(() => {
|
|
520
|
+
if (forcedType === 'plugins' || forcedType === 'skills') {
|
|
521
|
+
return forcedType;
|
|
522
|
+
}
|
|
516
523
|
if (params.type === 'plugins' || params.type === 'skills') {
|
|
517
524
|
return params.type;
|
|
518
525
|
}
|
|
519
526
|
return null;
|
|
520
|
-
}, [params.type]);
|
|
527
|
+
}, [forcedType, params.type]);
|
|
521
528
|
|
|
522
529
|
useEffect(() => {
|
|
530
|
+
if (forcedType) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
523
533
|
if (!routeType) {
|
|
524
534
|
navigate('/marketplace/plugins', { replace: true });
|
|
525
535
|
}
|
|
526
|
-
}, [routeType, navigate]);
|
|
536
|
+
}, [forcedType, routeType, navigate]);
|
|
527
537
|
|
|
528
538
|
const typeFilter: MarketplaceItemType = routeType === 'skills' ? 'skill' : 'plugin';
|
|
529
539
|
const localeFallbacks = useMemo(() => buildLocaleFallbacks(language), [language]);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
|
|
6
|
+
const Popover = PopoverPrimitive.Root;
|
|
7
|
+
|
|
8
|
+
const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
9
|
+
|
|
10
|
+
const PopoverAnchor = PopoverPrimitive.Anchor;
|
|
11
|
+
|
|
12
|
+
const PopoverContent = React.forwardRef<
|
|
13
|
+
React.ElementRef<typeof PopoverPrimitive.Content>,
|
|
14
|
+
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
|
15
|
+
>(({ className, sideOffset = 8, align = 'start', ...props }, ref) => (
|
|
16
|
+
<PopoverPrimitive.Portal>
|
|
17
|
+
<PopoverPrimitive.Content
|
|
18
|
+
ref={ref}
|
|
19
|
+
sideOffset={sideOffset}
|
|
20
|
+
align={align}
|
|
21
|
+
className={cn(
|
|
22
|
+
'z-[var(--z-popover,50)] w-72 overflow-hidden rounded-2xl border border-gray-200/50 bg-white p-4 shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
23
|
+
className
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
</PopoverPrimitive.Portal>
|
|
28
|
+
));
|
|
29
|
+
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
|
30
|
+
|
|
31
|
+
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };
|
package/src/hooks/useConfig.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
updateSession,
|
|
18
18
|
deleteSession,
|
|
19
19
|
sendChatTurn,
|
|
20
|
+
fetchChatCapabilities,
|
|
20
21
|
fetchCronJobs,
|
|
21
22
|
deleteCronJob,
|
|
22
23
|
setCronJobEnabled,
|
|
@@ -242,6 +243,23 @@ export function useSendChatTurn() {
|
|
|
242
243
|
});
|
|
243
244
|
}
|
|
244
245
|
|
|
246
|
+
export function useChatCapabilities(params?: { sessionKey?: string | null; agentId?: string | null }) {
|
|
247
|
+
const sessionKey = params?.sessionKey?.trim() || undefined;
|
|
248
|
+
const agentId = params?.agentId?.trim() || undefined;
|
|
249
|
+
return useQuery({
|
|
250
|
+
queryKey: ['chat-capabilities', sessionKey ?? null, agentId ?? null],
|
|
251
|
+
queryFn: async () => {
|
|
252
|
+
try {
|
|
253
|
+
return await fetchChatCapabilities({ sessionKey, agentId });
|
|
254
|
+
} catch {
|
|
255
|
+
return { stopSupported: false };
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
staleTime: 10_000,
|
|
259
|
+
retry: false
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
245
263
|
export function useCronJobs(params: { all?: boolean } = { all: true }) {
|
|
246
264
|
return useQuery({
|
|
247
265
|
queryKey: ['cron', params],
|
package/src/lib/i18n.ts
CHANGED
|
@@ -132,6 +132,8 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
132
132
|
runtime: { zh: '路由与运行时', en: 'Routing & Runtime' },
|
|
133
133
|
marketplace: { zh: '市场', en: 'Marketplace' },
|
|
134
134
|
advanced: { zh: '高级', en: 'Advanced' },
|
|
135
|
+
settings: { zh: '设置', en: 'Settings' },
|
|
136
|
+
backToMain: { zh: '返回主界面', en: 'Back to Main' },
|
|
135
137
|
|
|
136
138
|
// Common
|
|
137
139
|
enabled: { zh: '启用', en: 'Enabled' },
|
|
@@ -360,6 +362,8 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
360
362
|
dmScopeHelp: { zh: '控制私聊会话如何隔离。', en: 'Control how direct-message sessions are isolated.' },
|
|
361
363
|
defaultContextTokens: { zh: '默认上下文 Token', en: 'Default Context Tokens' },
|
|
362
364
|
defaultContextTokensHelp: { zh: '当 Agent 未设置单独值时使用该上下文预算。', en: 'Input context budget for agents when no per-agent override is set.' },
|
|
365
|
+
defaultEngine: { zh: '默认引擎', en: 'Default Engine' },
|
|
366
|
+
defaultEngineHelp: { zh: '默认使用的 Agent 引擎类型,例如 native 或 codex-sdk。', en: 'Default agent engine kind, for example native or codex-sdk.' },
|
|
363
367
|
maxPingPongTurns: { zh: '最大乒乓轮次', en: 'Max Ping-Pong Turns' },
|
|
364
368
|
maxPingPongTurnsHelp: { zh: '设为 0 可阻止 Agent 间自动 ping-pong。', en: 'Set to 0 to block automatic agent-to-agent ping-pong loops.' },
|
|
365
369
|
agentList: { zh: 'Agent 列表', en: 'Agent List' },
|
|
@@ -375,6 +379,8 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
375
379
|
agentIdPlaceholder: { zh: 'Agent ID(例如 engineer)', en: 'Agent ID (e.g. engineer)' },
|
|
376
380
|
workspaceOverridePlaceholder: { zh: '工作空间覆盖(可选)', en: 'Workspace override (optional)' },
|
|
377
381
|
modelOverridePlaceholder: { zh: '模型覆盖(可选)', en: 'Model override (optional)' },
|
|
382
|
+
defaultEnginePlaceholder: { zh: '默认引擎(如 native 或 codex-sdk)', en: 'Default engine (e.g. native or codex-sdk)' },
|
|
383
|
+
engineOverridePlaceholder: { zh: '引擎覆盖(可选)', en: 'Engine override (optional)' },
|
|
378
384
|
contextTokensPlaceholder: { zh: '上下文 tokens', en: 'Context tokens' },
|
|
379
385
|
maxToolsPlaceholder: { zh: '最大工具次数', en: 'Max tools' },
|
|
380
386
|
defaultAgent: { zh: '默认 Agent', en: 'Default agent' },
|
|
@@ -479,6 +485,15 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
479
485
|
chatSearchSessionPlaceholder: { zh: '搜索会话 key / 标签', en: 'Search session key / label' },
|
|
480
486
|
chatAgentLabel: { zh: '目标 Agent', en: 'Target Agent' },
|
|
481
487
|
chatSelectAgent: { zh: '选择 Agent', en: 'Select Agent' },
|
|
488
|
+
chatModelLabel: { zh: '对话模型', en: 'Chat Model' },
|
|
489
|
+
chatSelectModel: { zh: '选择模型', en: 'Select model' },
|
|
490
|
+
chatModelNoOptions: { zh: '暂无可用模型,请先配置提供商。', en: 'No available models. Configure a provider first.' },
|
|
491
|
+
chatGoConfigureProvider: { zh: '去配置提供商', en: 'Go to Providers' },
|
|
492
|
+
chatProviderSetupTitle: { zh: '开始前先配置提供商', en: 'Configure a Provider First' },
|
|
493
|
+
chatProviderSetupDescription: {
|
|
494
|
+
zh: '你还没有可用模型。先在提供商页面配置并保存至少一个 Provider 后,再回来开始对话。',
|
|
495
|
+
en: 'No models are available yet. Configure and save at least one provider, then return to start chatting.'
|
|
496
|
+
},
|
|
482
497
|
chatSessionLabel: { zh: '当前会话', en: 'Current Session' },
|
|
483
498
|
chatNoSession: { zh: '未选择会话', en: 'No session selected' },
|
|
484
499
|
chatNoSessionHint: { zh: '创建一个会话并发送第一条消息。', en: 'Create a session and send your first message.' },
|
|
@@ -488,6 +503,9 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
488
503
|
chatInputPlaceholder: { zh: '输入消息,Enter 发送,Shift + Enter 换行', en: 'Type a message, Enter to send, Shift + Enter for newline' },
|
|
489
504
|
chatInputHint: { zh: '支持多轮上下文,默认走当前会话。', en: 'Multi-turn context is preserved in the current session.' },
|
|
490
505
|
chatSend: { zh: '发送', en: 'Send' },
|
|
506
|
+
chatStop: { zh: '停止', en: 'Stop' },
|
|
507
|
+
chatStopPreparing: { zh: '正在建立可停止会话,请稍候…', en: 'Preparing stoppable run…' },
|
|
508
|
+
chatStopUnavailable: { zh: '当前后端引擎不支持手动停止。', en: 'Manual stop is not supported by the current backend engine.' },
|
|
491
509
|
chatSending: { zh: '发送中...', en: 'Sending...' },
|
|
492
510
|
chatQueueSend: { zh: '排队发送', en: 'Queue' },
|
|
493
511
|
chatQueuedHintPrefix: { zh: '当前有', en: 'Queued' },
|
|
@@ -502,10 +520,45 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
502
520
|
chatRoleMessage: { zh: '消息', en: 'Message' },
|
|
503
521
|
chatToolCall: { zh: '工具调用', en: 'Tool Call' },
|
|
504
522
|
chatToolResult: { zh: '工具结果', en: 'Tool Result' },
|
|
523
|
+
chatToolWorkflow: { zh: '工具工作流', en: 'Tool Workflow' },
|
|
524
|
+
chatToolWorkflowDetails: { zh: '展开查看参数和结果', en: 'Expand to view params and results' },
|
|
505
525
|
chatToolOutput: { zh: '查看输出', en: 'View Output' },
|
|
506
526
|
chatToolNoOutput: { zh: '无输出(执行完成)', en: 'No output (completed)' },
|
|
507
527
|
chatReasoning: { zh: '查看推理内容', en: 'Show reasoning' },
|
|
508
528
|
|
|
529
|
+
// Chat Sidebar (unified)
|
|
530
|
+
chatSidebarNewTask: { zh: '新任务', en: 'New Task' },
|
|
531
|
+
chatSidebarSearchPlaceholder: { zh: '搜索对话...', en: 'Search conversations...' },
|
|
532
|
+
chatSidebarScheduledTasks: { zh: '定时任务', en: 'Scheduled Tasks' },
|
|
533
|
+
chatSidebarSkills: { zh: '技能', en: 'Skills' },
|
|
534
|
+
chatSidebarTaskRecords: { zh: '会话记录', en: 'Sessions' },
|
|
535
|
+
chatSidebarToday: { zh: '今天', en: 'Today' },
|
|
536
|
+
chatSidebarYesterday: { zh: '昨天', en: 'Yesterday' },
|
|
537
|
+
chatSidebarPrevious7Days: { zh: '近 7 天', en: 'Previous 7 Days' },
|
|
538
|
+
chatSidebarOlder: { zh: '更早', en: 'Older' },
|
|
539
|
+
|
|
540
|
+
// Welcome page
|
|
541
|
+
chatWelcomeTitle: { zh: '你好,有什么可以帮你的吗?', en: 'Hello, how can I help you?' },
|
|
542
|
+
chatWelcomeSubtitle: { zh: '开始一个新任务或选择已有对话', en: 'Start a new task or select an existing conversation' },
|
|
543
|
+
chatWelcomeCapability1Title: { zh: '智能对话', en: 'Smart Conversations' },
|
|
544
|
+
chatWelcomeCapability1Desc: { zh: '多轮上下文对话,支持多种 AI 模型', en: 'Multi-turn context conversations with multiple AI models' },
|
|
545
|
+
chatWelcomeCapability2Title: { zh: '技能扩展', en: 'Skill Extensions' },
|
|
546
|
+
chatWelcomeCapability2Desc: { zh: '通过安装技能扩展 Agent 能力', en: 'Extend Agent capabilities by installing skills' },
|
|
547
|
+
chatWelcomeCapability3Title: { zh: '定时任务', en: 'Scheduled Tasks' },
|
|
548
|
+
chatWelcomeCapability3Desc: { zh: '设置定时执行的自动化任务', en: 'Set up scheduled automated tasks' },
|
|
549
|
+
|
|
550
|
+
// Skills picker
|
|
551
|
+
chatSkillsPickerTitle: { zh: '技能', en: 'Skills' },
|
|
552
|
+
chatSkillsPickerEmpty: { zh: '暂无已安装技能', en: 'No skills installed' },
|
|
553
|
+
chatSkillsPickerSearchPlaceholder: { zh: '搜索技能', en: 'Search skills' },
|
|
554
|
+
chatSkillsPickerNoDescription: { zh: '暂无描述', en: 'No description' },
|
|
555
|
+
chatSkillsPickerOfficial: { zh: '官方', en: 'Official' },
|
|
556
|
+
chatSkillsPickerManage: { zh: '管理技能', en: 'Manage Skills' },
|
|
557
|
+
|
|
558
|
+
// Input bar
|
|
559
|
+
chatInputAttach: { zh: '添加附件', en: 'Attach file' },
|
|
560
|
+
chatInputAttachComingSoon: { zh: '即将支持', en: 'Coming soon' },
|
|
561
|
+
|
|
509
562
|
// Cron
|
|
510
563
|
cronPageTitle: { zh: '定时任务', en: 'Cron Jobs' },
|
|
511
564
|
cronPageDescription: { zh: '查看与删除定时任务,关注执行时间与状态。', en: 'View and delete cron jobs, track schedule and status.' },
|