@nextclaw/ui 0.6.7 → 0.6.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.
- package/CHANGELOG.md +6 -0
- package/dist/assets/{ChannelsList-Dz8AGmaQ.js → ChannelsList-DH5fzlPu.js} +1 -1
- package/dist/assets/ChatPage-BrLCnJSb.js +34 -0
- package/dist/assets/{DocBrowser-CkKvzF7m.js → DocBrowser-DPQHJVsZ.js} +1 -1
- package/dist/assets/{LogoBadge-C_ygxoGB.js → LogoBadge-FEb4_vSq.js} +1 -1
- package/dist/assets/{MarketplacePage-DEvRs-Jc.js → MarketplacePage-BAVXYeZA.js} +1 -1
- package/dist/assets/{ModelConfig-BGfliN2Z.js → ModelConfig-BqPXe7nw.js} +1 -1
- package/dist/assets/{ProvidersList-BHLGLSvs.js → ProvidersList-vpKPuIxV.js} +1 -1
- package/dist/assets/{RuntimeConfig-Clltld_h.js → RuntimeConfig-DTYSU4_d.js} +1 -1
- package/dist/assets/{SecretsConfig-CaJLf7oJ.js → SecretsConfig-nNzs3YDm.js} +1 -1
- package/dist/assets/{SessionsConfig-3QF7K9wm.js → SessionsConfig-CHjeyqEQ.js} +1 -1
- package/dist/assets/{card-DXo3NsaB.js → card-73MmEZi7.js} +1 -1
- package/dist/assets/{index-CGo5Vnh0.js → index-CTLvVlk8.js} +5 -5
- package/dist/assets/{index-DcxYzrFm.css → index-DI6BuShn.css} +1 -1
- package/dist/assets/{input-CzTldMKo.js → input-1MCMs6Yf.js} +1 -1
- package/dist/assets/{label-De__vsU7.js → label-C4Q8RlBJ.js} +1 -1
- package/dist/assets/{page-layout-BOgLC2tK.js → page-layout-CK0vcVmV.js} +1 -1
- package/dist/assets/{session-run-status-DQVCDxTb.js → session-run-status-BaNlKvi6.js} +1 -1
- package/dist/assets/{switch-pMrS4heA.js → switch-Bf8w_cF1.js} +1 -1
- package/dist/assets/{tabs-custom-DhOxWfCb.js → tabs-custom-B6Gw8gax.js} +1 -1
- package/dist/assets/{useConfirmDialog-CseKBGh5.js → useConfirmDialog-B5CZ4EDN.js} +2 -2
- package/dist/assets/{vendor-D33xZtEC.js → vendor-C--HHaLf.js} +1 -1
- package/dist/index.html +3 -3
- package/package.json +1 -1
- package/src/components/chat/ChatInputBar.tsx +341 -24
- package/src/components/chat/ChatPage.tsx +13 -5
- package/src/lib/i18n.ts +11 -1
- package/dist/assets/ChatPage-BXDyt7BL.js +0 -34
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
1
2
|
import { Button } from '@/components/ui/button';
|
|
3
|
+
import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/popover';
|
|
2
4
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
3
5
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
4
6
|
import { SkillsPicker } from '@/components/chat/SkillsPicker';
|
|
@@ -6,6 +8,8 @@ import type { MarketplaceInstalledRecord } from '@/api/types';
|
|
|
6
8
|
import { t } from '@/lib/i18n';
|
|
7
9
|
import { Paperclip, Send, Sparkles, Square, X } from 'lucide-react';
|
|
8
10
|
|
|
11
|
+
const SLASH_PANEL_MAX_WIDTH = 920;
|
|
12
|
+
|
|
9
13
|
export type ChatModelOption = {
|
|
10
14
|
value: string;
|
|
11
15
|
modelLabel: string;
|
|
@@ -33,6 +37,91 @@ type ChatInputBarProps = {
|
|
|
33
37
|
onSelectedSkillsChange: (next: string[]) => void;
|
|
34
38
|
};
|
|
35
39
|
|
|
40
|
+
type SlashPanelItem = {
|
|
41
|
+
kind: 'skill';
|
|
42
|
+
key: string;
|
|
43
|
+
title: string;
|
|
44
|
+
subtitle: string;
|
|
45
|
+
description: string;
|
|
46
|
+
detailLines: string[];
|
|
47
|
+
skillSpec?: string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type RankedSkill = {
|
|
51
|
+
record: MarketplaceInstalledRecord;
|
|
52
|
+
score: number;
|
|
53
|
+
order: number;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function resolveSlashQuery(draft: string): string | null {
|
|
57
|
+
const match = /^\/([^\s]*)$/.exec(draft);
|
|
58
|
+
if (!match) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
return (match[1] ?? '').trim().toLowerCase();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeSearchText(value: string | null | undefined): string {
|
|
65
|
+
return (value ?? '').trim().toLowerCase();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isSubsequenceMatch(query: string, target: string): boolean {
|
|
69
|
+
if (!query || !target) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
let pointer = 0;
|
|
73
|
+
for (const char of target) {
|
|
74
|
+
if (char === query[pointer]) {
|
|
75
|
+
pointer += 1;
|
|
76
|
+
if (pointer >= query.length) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function scoreSkillRecord(record: MarketplaceInstalledRecord, query: string): number {
|
|
85
|
+
const normalizedQuery = normalizeSearchText(query);
|
|
86
|
+
if (!normalizedQuery) {
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const spec = normalizeSearchText(record.spec);
|
|
91
|
+
const label = normalizeSearchText(record.label || record.spec);
|
|
92
|
+
const description = normalizeSearchText(`${record.descriptionZh ?? ''} ${record.description ?? ''}`);
|
|
93
|
+
const labelTokens = label.split(/[\s/_-]+/).filter(Boolean);
|
|
94
|
+
|
|
95
|
+
if (spec === normalizedQuery) {
|
|
96
|
+
return 1200;
|
|
97
|
+
}
|
|
98
|
+
if (label === normalizedQuery) {
|
|
99
|
+
return 1150;
|
|
100
|
+
}
|
|
101
|
+
if (spec.startsWith(normalizedQuery)) {
|
|
102
|
+
return 1000;
|
|
103
|
+
}
|
|
104
|
+
if (label.startsWith(normalizedQuery)) {
|
|
105
|
+
return 950;
|
|
106
|
+
}
|
|
107
|
+
if (labelTokens.some((token) => token.startsWith(normalizedQuery))) {
|
|
108
|
+
return 900;
|
|
109
|
+
}
|
|
110
|
+
if (spec.includes(normalizedQuery)) {
|
|
111
|
+
return 800;
|
|
112
|
+
}
|
|
113
|
+
if (label.includes(normalizedQuery)) {
|
|
114
|
+
return 760;
|
|
115
|
+
}
|
|
116
|
+
if (description.includes(normalizedQuery)) {
|
|
117
|
+
return 500;
|
|
118
|
+
}
|
|
119
|
+
if (isSubsequenceMatch(normalizedQuery, label) || isSubsequenceMatch(normalizedQuery, spec)) {
|
|
120
|
+
return 300;
|
|
121
|
+
}
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
36
125
|
export function ChatInputBar({
|
|
37
126
|
isProviderStateResolved,
|
|
38
127
|
draft,
|
|
@@ -53,6 +142,11 @@ export function ChatInputBar({
|
|
|
53
142
|
selectedSkills,
|
|
54
143
|
onSelectedSkillsChange
|
|
55
144
|
}: ChatInputBarProps) {
|
|
145
|
+
const [activeSlashIndex, setActiveSlashIndex] = useState(0);
|
|
146
|
+
const [dismissedSlashPanel, setDismissedSlashPanel] = useState(false);
|
|
147
|
+
const [slashPanelWidth, setSlashPanelWidth] = useState<number | null>(null);
|
|
148
|
+
const slashAnchorRef = useRef<HTMLDivElement | null>(null);
|
|
149
|
+
const slashListRef = useRef<HTMLDivElement | null>(null);
|
|
56
150
|
const hasModelOptions = modelOptions.length > 0;
|
|
57
151
|
const isModelOptionsLoading = !isProviderStateResolved && !hasModelOptions;
|
|
58
152
|
const isModelOptionsEmpty = isProviderStateResolved && !hasModelOptions;
|
|
@@ -69,36 +163,259 @@ export function ChatInputBar({
|
|
|
69
163
|
label: matched?.label || spec
|
|
70
164
|
};
|
|
71
165
|
});
|
|
166
|
+
const slashQuery = useMemo(() => resolveSlashQuery(draft), [draft]);
|
|
167
|
+
const startsWithSlash = draft.startsWith('/');
|
|
168
|
+
const normalizedSlashQuery = slashQuery ?? '';
|
|
169
|
+
const skillSortCollator = useMemo(
|
|
170
|
+
() => new Intl.Collator(undefined, { sensitivity: 'base', numeric: true }),
|
|
171
|
+
[]
|
|
172
|
+
);
|
|
173
|
+
const skillSlashItems = useMemo<SlashPanelItem[]>(() => {
|
|
174
|
+
const rankedRecords: RankedSkill[] = skillRecords
|
|
175
|
+
.map((record, order) => ({
|
|
176
|
+
record,
|
|
177
|
+
score: scoreSkillRecord(record, normalizedSlashQuery),
|
|
178
|
+
order
|
|
179
|
+
}))
|
|
180
|
+
.filter((entry) => entry.score > 0)
|
|
181
|
+
.sort((left, right) => {
|
|
182
|
+
if (right.score !== left.score) {
|
|
183
|
+
return right.score - left.score;
|
|
184
|
+
}
|
|
185
|
+
const leftLabel = (left.record.label || left.record.spec).trim();
|
|
186
|
+
const rightLabel = (right.record.label || right.record.spec).trim();
|
|
187
|
+
const labelCompare = skillSortCollator.compare(leftLabel, rightLabel);
|
|
188
|
+
if (labelCompare !== 0) {
|
|
189
|
+
return labelCompare;
|
|
190
|
+
}
|
|
191
|
+
return left.order - right.order;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return rankedRecords
|
|
195
|
+
.map((entry) => entry.record)
|
|
196
|
+
.map((record) => ({
|
|
197
|
+
kind: 'skill',
|
|
198
|
+
key: `skill:${record.spec}`,
|
|
199
|
+
title: record.label || record.spec,
|
|
200
|
+
subtitle: t('chatSlashTypeSkill'),
|
|
201
|
+
description: (record.descriptionZh ?? record.description ?? '').trim() || t('chatSkillsPickerNoDescription'),
|
|
202
|
+
detailLines: [`${t('chatSlashSkillSpec')}: ${record.spec}`],
|
|
203
|
+
skillSpec: record.spec
|
|
204
|
+
}));
|
|
205
|
+
}, [normalizedSlashQuery, skillRecords, skillSortCollator]);
|
|
206
|
+
const slashItems = useMemo(() => [...skillSlashItems], [skillSlashItems]);
|
|
207
|
+
const isSlashPanelOpen = slashQuery !== null && !dismissedSlashPanel;
|
|
208
|
+
const activeSlashItem = slashItems[activeSlashIndex] ?? null;
|
|
209
|
+
const isSlashPanelLoading = isSkillsLoading;
|
|
210
|
+
const resolvedSlashPanelWidth = slashPanelWidth ? Math.min(slashPanelWidth, SLASH_PANEL_MAX_WIDTH) : undefined;
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
const anchor = slashAnchorRef.current;
|
|
214
|
+
if (!anchor || typeof ResizeObserver === 'undefined') {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const update = () => {
|
|
218
|
+
setSlashPanelWidth(anchor.getBoundingClientRect().width);
|
|
219
|
+
};
|
|
220
|
+
update();
|
|
221
|
+
const observer = new ResizeObserver(() => update());
|
|
222
|
+
observer.observe(anchor);
|
|
223
|
+
return () => {
|
|
224
|
+
observer.disconnect();
|
|
225
|
+
};
|
|
226
|
+
}, []);
|
|
227
|
+
|
|
228
|
+
useEffect(() => {
|
|
229
|
+
if (!isSlashPanelOpen) {
|
|
230
|
+
setActiveSlashIndex(0);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (slashItems.length === 0) {
|
|
234
|
+
setActiveSlashIndex(0);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
setActiveSlashIndex((current) => {
|
|
238
|
+
if (current < 0) {
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
if (current >= slashItems.length) {
|
|
242
|
+
return slashItems.length - 1;
|
|
243
|
+
}
|
|
244
|
+
return current;
|
|
245
|
+
});
|
|
246
|
+
}, [isSlashPanelOpen, slashItems.length]);
|
|
247
|
+
|
|
248
|
+
useEffect(() => {
|
|
249
|
+
if (!startsWithSlash && dismissedSlashPanel) {
|
|
250
|
+
setDismissedSlashPanel(false);
|
|
251
|
+
}
|
|
252
|
+
}, [dismissedSlashPanel, startsWithSlash]);
|
|
253
|
+
|
|
254
|
+
useEffect(() => {
|
|
255
|
+
if (!isSlashPanelOpen || isSlashPanelLoading || slashItems.length === 0) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const container = slashListRef.current;
|
|
259
|
+
if (!container) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const active = container.querySelector<HTMLElement>(`[data-slash-index="${activeSlashIndex}"]`);
|
|
263
|
+
active?.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
264
|
+
}, [activeSlashIndex, isSlashPanelLoading, isSlashPanelOpen, slashItems.length]);
|
|
265
|
+
|
|
266
|
+
const handleSelectSlashItem = useCallback((item: SlashPanelItem) => {
|
|
267
|
+
if (item.kind === 'skill' && item.skillSpec) {
|
|
268
|
+
if (!selectedSkills.includes(item.skillSpec)) {
|
|
269
|
+
onSelectedSkillsChange([...selectedSkills, item.skillSpec]);
|
|
270
|
+
}
|
|
271
|
+
onDraftChange('');
|
|
272
|
+
setDismissedSlashPanel(false);
|
|
273
|
+
}
|
|
274
|
+
}, [onDraftChange, onSelectedSkillsChange, selectedSkills]);
|
|
275
|
+
|
|
276
|
+
const handleSlashPanelOpenChange = useCallback((open: boolean) => {
|
|
277
|
+
if (!open) {
|
|
278
|
+
setDismissedSlashPanel(true);
|
|
279
|
+
}
|
|
280
|
+
}, []);
|
|
72
281
|
|
|
73
282
|
return (
|
|
74
283
|
<div className="border-t border-gray-200/80 bg-white p-4">
|
|
75
284
|
<div className="mx-auto w-full max-w-[min(1120px,100%)]">
|
|
76
285
|
<div className="rounded-2xl border border-gray-200 bg-white shadow-card overflow-hidden">
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
e.
|
|
85
|
-
|
|
86
|
-
|
|
286
|
+
<div className="relative">
|
|
287
|
+
{/* Textarea */}
|
|
288
|
+
<textarea
|
|
289
|
+
value={draft}
|
|
290
|
+
onChange={(e) => onDraftChange(e.target.value)}
|
|
291
|
+
disabled={inputDisabled}
|
|
292
|
+
onKeyDown={(e) => {
|
|
293
|
+
if (isSlashPanelOpen && !e.nativeEvent.isComposing && (e.key === ' ' || e.code === 'Space')) {
|
|
294
|
+
setDismissedSlashPanel(true);
|
|
295
|
+
}
|
|
296
|
+
if (isSlashPanelOpen && slashItems.length > 0) {
|
|
297
|
+
if (e.key === 'ArrowDown') {
|
|
298
|
+
e.preventDefault();
|
|
299
|
+
setActiveSlashIndex((current) => (current + 1) % slashItems.length);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (e.key === 'ArrowUp') {
|
|
303
|
+
e.preventDefault();
|
|
304
|
+
setActiveSlashIndex((current) => (current - 1 + slashItems.length) % slashItems.length);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if ((e.key === 'Enter' && !e.shiftKey) || e.key === 'Tab') {
|
|
308
|
+
e.preventDefault();
|
|
309
|
+
const selected = slashItems[activeSlashIndex];
|
|
310
|
+
if (selected) {
|
|
311
|
+
handleSelectSlashItem(selected);
|
|
312
|
+
}
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (e.key === 'Escape') {
|
|
317
|
+
if (isSlashPanelOpen) {
|
|
318
|
+
e.preventDefault();
|
|
319
|
+
setDismissedSlashPanel(true);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (isSending && canStopGeneration) {
|
|
323
|
+
e.preventDefault();
|
|
324
|
+
void onStop();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
329
|
+
e.preventDefault();
|
|
330
|
+
void onSend();
|
|
331
|
+
}
|
|
332
|
+
}}
|
|
333
|
+
placeholder={
|
|
334
|
+
isModelOptionsLoading
|
|
335
|
+
? ''
|
|
336
|
+
: hasModelOptions
|
|
337
|
+
? t('chatInputPlaceholder')
|
|
338
|
+
: t('chatModelNoOptions')
|
|
87
339
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
340
|
+
className="w-full min-h-[68px] max-h-[220px] resize-y bg-transparent outline-none text-sm px-4 py-3 text-gray-800 placeholder:text-gray-400"
|
|
341
|
+
/>
|
|
342
|
+
<Popover open={isSlashPanelOpen} onOpenChange={handleSlashPanelOpenChange}>
|
|
343
|
+
<PopoverAnchor asChild>
|
|
344
|
+
<div ref={slashAnchorRef} className="pointer-events-none absolute left-3 right-3 bottom-full h-0" />
|
|
345
|
+
</PopoverAnchor>
|
|
346
|
+
<PopoverContent
|
|
347
|
+
side="top"
|
|
348
|
+
align="start"
|
|
349
|
+
sideOffset={10}
|
|
350
|
+
className="z-[70] max-w-[calc(100vw-1.5rem)] overflow-hidden rounded-2xl border border-gray-200 bg-white/95 p-0 shadow-2xl backdrop-blur-md"
|
|
351
|
+
onOpenAutoFocus={(event) => event.preventDefault()}
|
|
352
|
+
style={resolvedSlashPanelWidth ? { width: `${resolvedSlashPanelWidth}px` } : undefined}
|
|
353
|
+
>
|
|
354
|
+
<div className="grid min-h-[240px] grid-cols-[minmax(260px,340px)_minmax(0,1fr)]">
|
|
355
|
+
<div ref={slashListRef} className="max-h-[320px] overflow-y-auto border-r border-gray-200 p-3 custom-scrollbar">
|
|
356
|
+
{isSlashPanelLoading ? (
|
|
357
|
+
<div className="p-2 text-xs text-gray-500">{t('chatSlashLoading')}</div>
|
|
358
|
+
) : (
|
|
359
|
+
<>
|
|
360
|
+
<div className="mb-2 px-2 text-[11px] font-semibold uppercase tracking-wide text-gray-500">
|
|
361
|
+
{t('chatSlashSectionSkills')}
|
|
362
|
+
</div>
|
|
363
|
+
{skillSlashItems.length === 0 ? (
|
|
364
|
+
<div className="px-2 text-xs text-gray-400">{t('chatSlashNoResult')}</div>
|
|
365
|
+
) : (
|
|
366
|
+
<div className="space-y-1">
|
|
367
|
+
{skillSlashItems.map((item, index) => {
|
|
368
|
+
const isActive = index === activeSlashIndex;
|
|
369
|
+
return (
|
|
370
|
+
<button
|
|
371
|
+
key={item.key}
|
|
372
|
+
type="button"
|
|
373
|
+
data-slash-index={index}
|
|
374
|
+
onMouseEnter={() => setActiveSlashIndex(index)}
|
|
375
|
+
onClick={() => handleSelectSlashItem(item)}
|
|
376
|
+
className={`flex w-full items-start gap-2 rounded-lg px-2 py-1.5 text-left transition ${
|
|
377
|
+
isActive ? 'bg-gray-100 text-gray-900' : 'text-gray-700 hover:bg-gray-50'
|
|
378
|
+
}`}
|
|
379
|
+
>
|
|
380
|
+
<span className="truncate text-xs font-semibold">{item.title}</span>
|
|
381
|
+
<span className="truncate text-xs text-gray-500">{item.subtitle}</span>
|
|
382
|
+
</button>
|
|
383
|
+
);
|
|
384
|
+
})}
|
|
385
|
+
</div>
|
|
386
|
+
)}
|
|
387
|
+
</>
|
|
388
|
+
)}
|
|
389
|
+
</div>
|
|
390
|
+
<div className="p-4">
|
|
391
|
+
{activeSlashItem ? (
|
|
392
|
+
<div className="space-y-3">
|
|
393
|
+
<div className="flex items-center gap-2">
|
|
394
|
+
<span className="inline-flex rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-semibold text-primary">
|
|
395
|
+
{activeSlashItem.subtitle}
|
|
396
|
+
</span>
|
|
397
|
+
<span className="text-sm font-semibold text-gray-900">{activeSlashItem.title}</span>
|
|
398
|
+
</div>
|
|
399
|
+
<p className="text-xs leading-5 text-gray-600">{activeSlashItem.description}</p>
|
|
400
|
+
<div className="space-y-1">
|
|
401
|
+
{activeSlashItem.detailLines.map((line) => (
|
|
402
|
+
<div key={line} className="rounded-md bg-gray-50 px-2 py-1 text-[11px] text-gray-600">
|
|
403
|
+
{line}
|
|
404
|
+
</div>
|
|
405
|
+
))}
|
|
406
|
+
</div>
|
|
407
|
+
<div className="pt-1 text-[11px] text-gray-500">
|
|
408
|
+
{t('chatSlashSkillHint')}
|
|
409
|
+
</div>
|
|
410
|
+
</div>
|
|
411
|
+
) : (
|
|
412
|
+
<div className="text-xs text-gray-500">{t('chatSlashHint')}</div>
|
|
413
|
+
)}
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
</PopoverContent>
|
|
417
|
+
</Popover>
|
|
418
|
+
</div>
|
|
102
419
|
{isModelOptionsLoading && (
|
|
103
420
|
<div className="px-4 pb-2">
|
|
104
421
|
<div className="inline-flex items-center gap-2 rounded-lg border border-gray-200 bg-gray-50 px-3 py-2">
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
useDeleteSession,
|
|
9
9
|
useSessionHistory,
|
|
10
10
|
useSessions,
|
|
11
|
-
useChatRuns
|
|
11
|
+
useChatRuns,
|
|
12
12
|
} from '@/hooks/useConfig';
|
|
13
13
|
import { useMarketplaceInstalled } from '@/hooks/useMarketplace';
|
|
14
14
|
import { useConfirmDialog } from '@/hooks/useConfirmDialog';
|
|
@@ -209,11 +209,19 @@ function ChatPageLayout({ view, sidebarProps, conversationProps, confirmDialog }
|
|
|
209
209
|
<ChatConversationPanel {...conversationProps} />
|
|
210
210
|
) : (
|
|
211
211
|
<section className="flex-1 min-h-0 overflow-hidden bg-gradient-to-b from-gray-50/60 to-white">
|
|
212
|
-
|
|
213
|
-
<div className="
|
|
214
|
-
|
|
212
|
+
{view === 'cron' ? (
|
|
213
|
+
<div className="h-full overflow-auto custom-scrollbar">
|
|
214
|
+
<div className="mx-auto w-full max-w-[min(1120px,100%)] px-6 py-5">
|
|
215
|
+
<CronConfig />
|
|
216
|
+
</div>
|
|
215
217
|
</div>
|
|
216
|
-
|
|
218
|
+
) : (
|
|
219
|
+
<div className="h-full overflow-hidden">
|
|
220
|
+
<div className="mx-auto flex h-full min-h-0 w-full max-w-[min(1120px,100%)] flex-col px-6 py-5">
|
|
221
|
+
<MarketplacePage forcedType="skills" />
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
217
225
|
</section>
|
|
218
226
|
)}
|
|
219
227
|
|
package/src/lib/i18n.ts
CHANGED
|
@@ -517,8 +517,18 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
|
|
|
517
517
|
chatHistoryLoading: { zh: '加载会话历史中...', en: 'Loading session history...' },
|
|
518
518
|
chatNoMessages: { zh: '暂无消息,发送一条开始对话。', en: 'No messages yet. Send one to start.' },
|
|
519
519
|
chatTyping: { zh: 'Agent 正在思考...', en: 'Agent is thinking...' },
|
|
520
|
-
chatInputPlaceholder: { zh: '
|
|
520
|
+
chatInputPlaceholder: { zh: '输入消息,输入 / 选择技能,Enter 发送,Shift + Enter 换行', en: 'Type a message, type / to select skills, Enter to send, Shift + Enter for newline' },
|
|
521
521
|
chatInputHint: { zh: '支持多轮上下文,默认走当前会话。', en: 'Multi-turn context is preserved in the current session.' },
|
|
522
|
+
chatSlashSectionCommands: { zh: '命令', en: 'Commands' },
|
|
523
|
+
chatSlashSectionSkills: { zh: '技能', en: 'Skills' },
|
|
524
|
+
chatSlashTypeCommand: { zh: '命令', en: 'Command' },
|
|
525
|
+
chatSlashTypeSkill: { zh: '技能', en: 'Skill' },
|
|
526
|
+
chatSlashSkillSpec: { zh: '标识', en: 'Spec' },
|
|
527
|
+
chatSlashLoading: { zh: '加载命令与技能中…', en: 'Loading commands and skills…' },
|
|
528
|
+
chatSlashNoResult: { zh: '无匹配项', en: 'No matches' },
|
|
529
|
+
chatSlashHint: { zh: '输入 / 触发命令或技能选择', en: 'Type / to access commands and skills' },
|
|
530
|
+
chatSlashCommandHint: { zh: '回车插入命令,继续输入参数后发送。', en: 'Press Enter to insert command, then add args and send.' },
|
|
531
|
+
chatSlashSkillHint: { zh: '回车把该技能加入本轮请求。', en: 'Press Enter to add this skill for the next turn.' },
|
|
522
532
|
chatSend: { zh: '发送', en: 'Send' },
|
|
523
533
|
chatStop: { zh: '停止', en: 'Stop' },
|
|
524
534
|
chatStopPreparing: { zh: '正在建立可停止会话,请稍候…', en: 'Preparing stoppable run…' },
|