@allbluecn/web-app 0.4.8 → 0.4.10
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/package.json +11 -8
- package/src/components/chat/ai-chat-history/components/conversation-item.tsx +1 -1
- package/src/components/chat/ai-chat-history/index.tsx +1 -1
- package/src/components/{command-palette.tsx → command/command-palette.tsx} +95 -47
- package/src/components/hextaui/command-menu.tsx +460 -0
- package/src/components/knowledge/editor/knowledge-doc-editor.tsx +4 -4
- package/src/components/knowledge/knowledge-settings-panel.tsx +2 -2
- package/src/components/knowledge/knowledge-toolbar.tsx +4 -4
- package/src/components/knowledge/knowledge-tree-item.tsx +3 -3
- package/src/components/knowledge/knowledge-tree.tsx +2 -2
- package/src/components/layout/sidebar-03/sidebar-search-trigger.tsx +21 -7
- package/src/components/settings/personal-team/account/account-client.tsx +1 -1
- package/src/components/story/detail/activity-timeline.tsx +7 -6
- package/src/lib/anyhub/manager.ts +17 -4
- package/src/lib/auth/auth.config.ts +2 -2
- package/src/lib/auth/sso-authorize.ts +1 -1
- package/src/lib/commands/registry.ts +2 -2
- package/src/lib/sanitize.ts +5 -0
- package/src/routes/_nav/chat/route.lazy.tsx +1 -1
- package/src/routes/_nav/design/index.lazy.tsx +1 -1
- package/src/routes/_nav/route.lazy.tsx +1 -1
- package/src/routes/_nav/settings/ai/route.tsx +1 -1
- package/src/routes/api/design/upload.ts +9 -1
- package/src/routes/design-files/$projectId/$artboardId/$filename.ts +6 -2
- package/src/routes/server.ai-stream.ts +1 -1
- package/src/server/audit.ts +1 -1
- package/src/server/keepalive/middleware.ts +1 -1
- package/src/server/knowledge-public/github-sync.ts +21 -3
- package/src/server/middlewares/audit-recorder.ts +1 -1
- package/src/server/middlewares/rate-limit.ts +27 -0
- package/src/server/references/engine.ts +11 -8
- package/src/server/serverFns/auth/forgot-password.ts +3 -1
- package/src/server/serverFns/auth/invitation.ts +1 -0
- package/src/server/serverFns/auth/register.ts +2 -0
- package/src/server/serverFns/auth/reset-password.ts +2 -0
- package/src/server/serverFns/auth/session.ts +2 -1
- package/src/server/serverFns/billing.ts +1 -1
- package/src/server/serverFns/compliance.ts +3 -2
- package/src/server/serverFns/design/settings.ts +1 -0
- package/src/server/serverFns/team.ts +1 -1
- package/src/server/sso/tokens.ts +18 -3
- package/src/start.ts +2 -2
- package/vite.shared.ts +1 -1
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
4
|
+
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
|
|
5
|
+
import { Search, X, Command, Option, ArrowBigUp, ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Delete, Space } from "lucide-react";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
9
|
+
|
|
10
|
+
interface CommandMenuContextType {
|
|
11
|
+
value: string;
|
|
12
|
+
setValue: (value: string) => void;
|
|
13
|
+
selectedIndex: number;
|
|
14
|
+
setSelectedIndex: (index: number) => void;
|
|
15
|
+
scrollType?: "auto" | "always" | "scroll" | "hover";
|
|
16
|
+
scrollHideDelay?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const CommandMenuContext = React.createContext<
|
|
20
|
+
CommandMenuContextType | undefined
|
|
21
|
+
>(undefined);
|
|
22
|
+
|
|
23
|
+
const CommandMenuProvider: React.FC<{
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
value: string;
|
|
26
|
+
setValue: (value: string) => void;
|
|
27
|
+
selectedIndex: number;
|
|
28
|
+
setSelectedIndex: (index: number) => void;
|
|
29
|
+
scrollType?: "auto" | "always" | "scroll" | "hover";
|
|
30
|
+
scrollHideDelay?: number;
|
|
31
|
+
}> = ({
|
|
32
|
+
children,
|
|
33
|
+
value,
|
|
34
|
+
setValue,
|
|
35
|
+
selectedIndex,
|
|
36
|
+
setSelectedIndex,
|
|
37
|
+
scrollType,
|
|
38
|
+
scrollHideDelay,
|
|
39
|
+
}) => (
|
|
40
|
+
<CommandMenuContext.Provider
|
|
41
|
+
value={{
|
|
42
|
+
value,
|
|
43
|
+
setValue,
|
|
44
|
+
selectedIndex,
|
|
45
|
+
setSelectedIndex,
|
|
46
|
+
scrollType,
|
|
47
|
+
scrollHideDelay,
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</CommandMenuContext.Provider>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const useCommandMenu = () => {
|
|
55
|
+
const context = React.useContext(CommandMenuContext);
|
|
56
|
+
if (!context)
|
|
57
|
+
throw new Error("useCommandMenu must be used within CommandMenuProvider");
|
|
58
|
+
return context;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const CommandMenu = DialogPrimitive.Root;
|
|
62
|
+
const CommandMenuTrigger = DialogPrimitive.Trigger;
|
|
63
|
+
const CommandMenuPortal = DialogPrimitive.Portal;
|
|
64
|
+
const CommandMenuClose = DialogPrimitive.Close;
|
|
65
|
+
|
|
66
|
+
const CommandMenuTitle = React.forwardRef<
|
|
67
|
+
React.ComponentRef<typeof DialogPrimitive.Title>,
|
|
68
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
69
|
+
>(({ className, ...props }, ref) => (
|
|
70
|
+
<DialogPrimitive.Title
|
|
71
|
+
className={cn(
|
|
72
|
+
"font-semibold text-foreground text-lg leading-none tracking-tight",
|
|
73
|
+
className
|
|
74
|
+
)}
|
|
75
|
+
ref={ref}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
));
|
|
79
|
+
CommandMenuTitle.displayName = "CommandMenuTitle";
|
|
80
|
+
|
|
81
|
+
const CommandMenuDescription = React.forwardRef<
|
|
82
|
+
React.ComponentRef<typeof DialogPrimitive.Description>,
|
|
83
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
84
|
+
>(({ className, ...props }, ref) => (
|
|
85
|
+
<DialogPrimitive.Description
|
|
86
|
+
className={cn("text-muted-foreground text-sm", className)}
|
|
87
|
+
ref={ref}
|
|
88
|
+
{...props}
|
|
89
|
+
/>
|
|
90
|
+
));
|
|
91
|
+
CommandMenuDescription.displayName = "CommandMenuDescription";
|
|
92
|
+
|
|
93
|
+
const CommandMenuOverlay = React.forwardRef<
|
|
94
|
+
React.ComponentRef<typeof DialogPrimitive.Overlay>,
|
|
95
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
96
|
+
>(({ className, ...props }, ref) => (
|
|
97
|
+
<DialogPrimitive.Overlay
|
|
98
|
+
className={cn(
|
|
99
|
+
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 backdrop-blur-sm data-[state=closed]:animate-out data-[state=open]:animate-in",
|
|
100
|
+
className
|
|
101
|
+
)}
|
|
102
|
+
ref={ref}
|
|
103
|
+
{...props}
|
|
104
|
+
/>
|
|
105
|
+
));
|
|
106
|
+
CommandMenuOverlay.displayName = "CommandMenuOverlay";
|
|
107
|
+
|
|
108
|
+
const CommandMenuContent = React.forwardRef<
|
|
109
|
+
React.ComponentRef<typeof DialogPrimitive.Content>,
|
|
110
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
|
|
111
|
+
showShortcut?: boolean;
|
|
112
|
+
scrollType?: "auto" | "always" | "scroll" | "hover";
|
|
113
|
+
scrollHideDelay?: number;
|
|
114
|
+
}
|
|
115
|
+
>(
|
|
116
|
+
(
|
|
117
|
+
{
|
|
118
|
+
className,
|
|
119
|
+
children,
|
|
120
|
+
showShortcut = true,
|
|
121
|
+
scrollType = "hover",
|
|
122
|
+
scrollHideDelay = 600,
|
|
123
|
+
...props
|
|
124
|
+
},
|
|
125
|
+
ref
|
|
126
|
+
) => {
|
|
127
|
+
const [value, setValue] = React.useState("");
|
|
128
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
129
|
+
|
|
130
|
+
React.useEffect(() => {
|
|
131
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
132
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Enter") {
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
137
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
138
|
+
}, []);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<CommandMenuPortal>
|
|
142
|
+
<CommandMenuOverlay />
|
|
143
|
+
<DialogPrimitive.Content asChild ref={ref} {...props}>
|
|
144
|
+
<div
|
|
145
|
+
className={cn(
|
|
146
|
+
"fixed top-[30%] left-1/2 z-50 w-auto max-w-none -translate-x-1/2 -translate-y-1/2",
|
|
147
|
+
"rounded-xl border border-border bg-background",
|
|
148
|
+
"overflow-hidden",
|
|
149
|
+
"motion-safe:duration-200 motion-reduce:animate-none",
|
|
150
|
+
className
|
|
151
|
+
)}
|
|
152
|
+
>
|
|
153
|
+
<CommandMenuProvider
|
|
154
|
+
scrollHideDelay={scrollHideDelay}
|
|
155
|
+
scrollType={scrollType}
|
|
156
|
+
selectedIndex={selectedIndex}
|
|
157
|
+
setSelectedIndex={setSelectedIndex}
|
|
158
|
+
setValue={setValue}
|
|
159
|
+
value={value}
|
|
160
|
+
>
|
|
161
|
+
<VisuallyHidden.Root>
|
|
162
|
+
<CommandMenuTitle>Command Menu</CommandMenuTitle>
|
|
163
|
+
</VisuallyHidden.Root>
|
|
164
|
+
{children}
|
|
165
|
+
<CommandMenuClose className="absolute top-3 right-3 rounded-lg p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
166
|
+
<X size={14} />
|
|
167
|
+
<span className="sr-only">Close</span>
|
|
168
|
+
</CommandMenuClose>
|
|
169
|
+
{showShortcut && (
|
|
170
|
+
<div className="absolute top-3 right-12 flex h-6 items-center justify-center gap-1">
|
|
171
|
+
<kbd
|
|
172
|
+
data-slot="kbd"
|
|
173
|
+
className="inline-flex items-center justify-center rounded px-1.5 py-1 font-mono text-[10px] font-medium text-muted-foreground bg-muted h-[22px] min-w-[22px] leading-none"
|
|
174
|
+
>
|
|
175
|
+
<Command size={10} />
|
|
176
|
+
</kbd>
|
|
177
|
+
<kbd
|
|
178
|
+
data-slot="kbd"
|
|
179
|
+
className="inline-flex items-center justify-center rounded px-1.5 py-1 font-mono text-[10px] font-medium text-muted-foreground bg-muted h-[22px] min-w-[22px] leading-none"
|
|
180
|
+
>
|
|
181
|
+
<span className="text-xs font-medium">K</span>
|
|
182
|
+
</kbd>
|
|
183
|
+
<span className="text-xs text-muted-foreground">/</span>
|
|
184
|
+
<kbd
|
|
185
|
+
data-slot="kbd"
|
|
186
|
+
className="inline-flex items-center justify-center rounded px-1.5 py-1 font-mono text-[10px] font-medium text-muted-foreground bg-muted h-[22px] min-w-[22px] leading-none"
|
|
187
|
+
>
|
|
188
|
+
<span className="text-xs font-medium">ESC</span>
|
|
189
|
+
</kbd>
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</CommandMenuProvider>
|
|
193
|
+
</div>
|
|
194
|
+
</DialogPrimitive.Content>
|
|
195
|
+
</CommandMenuPortal>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
CommandMenuContent.displayName = "CommandMenuContent";
|
|
200
|
+
|
|
201
|
+
const CommandMenuInput = React.forwardRef<
|
|
202
|
+
HTMLInputElement,
|
|
203
|
+
React.InputHTMLAttributes<HTMLInputElement> & { placeholder?: string }
|
|
204
|
+
>(
|
|
205
|
+
(
|
|
206
|
+
{ className, placeholder = "Type a command or search...", ...props },
|
|
207
|
+
ref
|
|
208
|
+
) => {
|
|
209
|
+
const { value, setValue } = useCommandMenu();
|
|
210
|
+
return (
|
|
211
|
+
<div className="flex items-center gap-2 border-border border-b px-3 py-0">
|
|
212
|
+
<Search className="size-4 shrink-0 text-muted-foreground" />
|
|
213
|
+
<input
|
|
214
|
+
className={cn(
|
|
215
|
+
"h-12 w-full rounded-none border-0 bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
|
216
|
+
className
|
|
217
|
+
)}
|
|
218
|
+
onChange={(e) => setValue(e.target.value)}
|
|
219
|
+
placeholder={placeholder}
|
|
220
|
+
ref={ref}
|
|
221
|
+
value={value}
|
|
222
|
+
{...props}
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
CommandMenuInput.displayName = "CommandMenuInput";
|
|
229
|
+
|
|
230
|
+
const CommandMenuList = React.forwardRef<
|
|
231
|
+
HTMLDivElement,
|
|
232
|
+
React.HTMLAttributes<HTMLDivElement> & { maxHeight?: string }
|
|
233
|
+
>(({ className, children, maxHeight = "300px", ...props }, ref) => {
|
|
234
|
+
const {
|
|
235
|
+
selectedIndex,
|
|
236
|
+
setSelectedIndex,
|
|
237
|
+
scrollType = "hover",
|
|
238
|
+
scrollHideDelay = 600,
|
|
239
|
+
} = useCommandMenu();
|
|
240
|
+
|
|
241
|
+
React.useEffect(() => {
|
|
242
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
243
|
+
const items = document.querySelectorAll("[data-command-item]");
|
|
244
|
+
const maxIndex = items.length - 1;
|
|
245
|
+
if (e.key === "ArrowDown") {
|
|
246
|
+
e.preventDefault();
|
|
247
|
+
const newIndex = Math.min(selectedIndex + 1, maxIndex);
|
|
248
|
+
setSelectedIndex(newIndex);
|
|
249
|
+
(items[newIndex] as HTMLElement | undefined)?.scrollIntoView({
|
|
250
|
+
block: "nearest",
|
|
251
|
+
behavior: "smooth",
|
|
252
|
+
});
|
|
253
|
+
} else if (e.key === "ArrowUp") {
|
|
254
|
+
e.preventDefault();
|
|
255
|
+
const newIndex = Math.max(selectedIndex - 1, 0);
|
|
256
|
+
setSelectedIndex(newIndex);
|
|
257
|
+
(items[newIndex] as HTMLElement | undefined)?.scrollIntoView({
|
|
258
|
+
block: "nearest",
|
|
259
|
+
behavior: "smooth",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
264
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
265
|
+
}, [selectedIndex, setSelectedIndex]);
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<div className="p-1" ref={ref} {...props}>
|
|
269
|
+
<ScrollArea
|
|
270
|
+
className={cn("w-full", className)}
|
|
271
|
+
scrollHideDelay={scrollHideDelay}
|
|
272
|
+
style={{ height: maxHeight }}
|
|
273
|
+
type={scrollType}
|
|
274
|
+
>
|
|
275
|
+
<div className="flex flex-col gap-0.5 p-0.5">{children}</div>
|
|
276
|
+
</ScrollArea>
|
|
277
|
+
</div>
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
CommandMenuList.displayName = "CommandMenuList";
|
|
281
|
+
|
|
282
|
+
const CommandMenuGroup = React.forwardRef<
|
|
283
|
+
HTMLDivElement,
|
|
284
|
+
React.HTMLAttributes<HTMLDivElement> & { heading?: string }
|
|
285
|
+
>(({ className, children, heading, ...props }, ref) => (
|
|
286
|
+
<div className={cn("", className)} ref={ref} {...props}>
|
|
287
|
+
{heading && (
|
|
288
|
+
<div className="px-2 py-1.5 font-medium text-muted-foreground text-xs uppercase tracking-wider">
|
|
289
|
+
{heading}
|
|
290
|
+
</div>
|
|
291
|
+
)}
|
|
292
|
+
{children}
|
|
293
|
+
</div>
|
|
294
|
+
));
|
|
295
|
+
CommandMenuGroup.displayName = "CommandMenuGroup";
|
|
296
|
+
|
|
297
|
+
const CommandMenuItem = React.forwardRef<
|
|
298
|
+
HTMLDivElement,
|
|
299
|
+
React.HTMLAttributes<HTMLDivElement> & {
|
|
300
|
+
onSelect?: () => void;
|
|
301
|
+
disabled?: boolean;
|
|
302
|
+
shortcut?: string;
|
|
303
|
+
icon?: React.ReactNode;
|
|
304
|
+
index?: number;
|
|
305
|
+
}
|
|
306
|
+
>(
|
|
307
|
+
(
|
|
308
|
+
{
|
|
309
|
+
className,
|
|
310
|
+
children,
|
|
311
|
+
onSelect,
|
|
312
|
+
disabled = false,
|
|
313
|
+
shortcut,
|
|
314
|
+
icon,
|
|
315
|
+
index = 0,
|
|
316
|
+
...props
|
|
317
|
+
},
|
|
318
|
+
ref
|
|
319
|
+
) => {
|
|
320
|
+
const { selectedIndex, setSelectedIndex } = useCommandMenu();
|
|
321
|
+
const isSelected = selectedIndex === index;
|
|
322
|
+
|
|
323
|
+
const handleSelect = React.useCallback(() => {
|
|
324
|
+
if (!disabled && onSelect) onSelect();
|
|
325
|
+
}, [disabled, onSelect]);
|
|
326
|
+
|
|
327
|
+
React.useEffect(() => {
|
|
328
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
329
|
+
if (e.key === "Enter" && isSelected) {
|
|
330
|
+
e.preventDefault();
|
|
331
|
+
handleSelect();
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
335
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
336
|
+
}, [isSelected, handleSelect]);
|
|
337
|
+
|
|
338
|
+
return (
|
|
339
|
+
<div
|
|
340
|
+
className={cn(
|
|
341
|
+
"relative flex cursor-default select-none items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none transition-colors",
|
|
342
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
343
|
+
isSelected && "bg-accent text-accent-foreground",
|
|
344
|
+
disabled && "pointer-events-none opacity-50",
|
|
345
|
+
className
|
|
346
|
+
)}
|
|
347
|
+
data-command-item
|
|
348
|
+
onClick={handleSelect}
|
|
349
|
+
onMouseEnter={() => setSelectedIndex(index)}
|
|
350
|
+
ref={ref}
|
|
351
|
+
{...props}
|
|
352
|
+
>
|
|
353
|
+
{icon && (
|
|
354
|
+
<div className="flex size-4 items-center justify-center">{icon}</div>
|
|
355
|
+
)}
|
|
356
|
+
<div className="flex-1">{children}</div>
|
|
357
|
+
{shortcut && (
|
|
358
|
+
<div className="ml-auto flex items-center gap-1">
|
|
359
|
+
{shortcut.split("+").map((key, i) => (
|
|
360
|
+
<React.Fragment key={`${key}-${i}`}>
|
|
361
|
+
{i > 0 && (
|
|
362
|
+
<span className="text-muted-foreground text-xs">+</span>
|
|
363
|
+
)}
|
|
364
|
+
<kbd
|
|
365
|
+
data-slot="kbd"
|
|
366
|
+
className={cn(
|
|
367
|
+
"inline-flex items-center justify-center rounded px-1.5 py-1 font-mono text-[10px] font-medium text-muted-foreground bg-muted h-[22px] min-w-[22px] leading-none"
|
|
368
|
+
)}
|
|
369
|
+
>
|
|
370
|
+
{key === "CMD" || key === "⌘" ? (
|
|
371
|
+
<Command size={10} />
|
|
372
|
+
) : key === "OPTION" || key === "ALT" ? (
|
|
373
|
+
<Option size={14} />
|
|
374
|
+
) : key === "SHIFT" ? (
|
|
375
|
+
<ArrowBigUp size={13} />
|
|
376
|
+
) : key === "CTRL" ? (
|
|
377
|
+
<span className="text-xs font-medium">CTRL</span>
|
|
378
|
+
) : key === "ESC" || key === "ESCAPE" ? (
|
|
379
|
+
<span className="text-xs font-medium">ESC</span>
|
|
380
|
+
) : key === "DELETE" || key === "BACKSPACE" ? (
|
|
381
|
+
<Delete size={14} />
|
|
382
|
+
) : key === "ARROWUP" ? (
|
|
383
|
+
<ArrowUp size={14} />
|
|
384
|
+
) : key === "ARROWDOWN" ? (
|
|
385
|
+
<ArrowDown size={14} />
|
|
386
|
+
) : key === "ARROWLEFT" ? (
|
|
387
|
+
<ArrowLeft size={14} />
|
|
388
|
+
) : key === "ARROWRIGHT" ? (
|
|
389
|
+
<ArrowRight size={14} />
|
|
390
|
+
) : key === "SPACE" ? (
|
|
391
|
+
<Space size={14} />
|
|
392
|
+
) : (
|
|
393
|
+
<span className="text-xs font-medium">{key}</span>
|
|
394
|
+
)}
|
|
395
|
+
</kbd>
|
|
396
|
+
</React.Fragment>
|
|
397
|
+
))}
|
|
398
|
+
</div>
|
|
399
|
+
)}
|
|
400
|
+
</div>
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
);
|
|
404
|
+
CommandMenuItem.displayName = "CommandMenuItem";
|
|
405
|
+
|
|
406
|
+
const CommandMenuSeparator = React.forwardRef<
|
|
407
|
+
HTMLDivElement,
|
|
408
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
409
|
+
>(({ className, ...props }, ref) => (
|
|
410
|
+
<div
|
|
411
|
+
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
|
412
|
+
ref={ref}
|
|
413
|
+
{...props}
|
|
414
|
+
/>
|
|
415
|
+
));
|
|
416
|
+
CommandMenuSeparator.displayName = "CommandMenuSeparator";
|
|
417
|
+
|
|
418
|
+
const CommandMenuEmpty = React.forwardRef<
|
|
419
|
+
HTMLDivElement,
|
|
420
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
421
|
+
>(({ className, children = "No results found.", ...props }, ref) => (
|
|
422
|
+
<div
|
|
423
|
+
className={cn("py-6 text-center text-muted-foreground text-sm", className)}
|
|
424
|
+
ref={ref}
|
|
425
|
+
{...props}
|
|
426
|
+
>
|
|
427
|
+
{children}
|
|
428
|
+
</div>
|
|
429
|
+
));
|
|
430
|
+
CommandMenuEmpty.displayName = "CommandMenuEmpty";
|
|
431
|
+
|
|
432
|
+
export const useCommandMenuShortcut = (callback: () => void) => {
|
|
433
|
+
React.useEffect(() => {
|
|
434
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
435
|
+
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
|
436
|
+
e.preventDefault();
|
|
437
|
+
callback();
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
441
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
442
|
+
}, [callback]);
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
export {
|
|
446
|
+
CommandMenu,
|
|
447
|
+
CommandMenuTrigger,
|
|
448
|
+
CommandMenuContent,
|
|
449
|
+
CommandMenuTitle,
|
|
450
|
+
CommandMenuDescription,
|
|
451
|
+
CommandMenuInput,
|
|
452
|
+
CommandMenuList,
|
|
453
|
+
CommandMenuEmpty,
|
|
454
|
+
CommandMenuGroup,
|
|
455
|
+
CommandMenuItem,
|
|
456
|
+
CommandMenuSeparator,
|
|
457
|
+
CommandMenuClose,
|
|
458
|
+
CommandMenuProvider,
|
|
459
|
+
useCommandMenu,
|
|
460
|
+
};
|
|
@@ -139,7 +139,7 @@ function KnowledgeDocEditorBody({ document, editorWidth = 'auto', kbName, proper
|
|
|
139
139
|
setDirty(false)
|
|
140
140
|
setLastSavedAt(new Date())
|
|
141
141
|
} catch (e) {
|
|
142
|
-
console.error('Auto-save failed:', e)
|
|
142
|
+
console.error('Auto-save failed:', e instanceof Error ? e.message : String(e))
|
|
143
143
|
} finally {
|
|
144
144
|
setSaving(false)
|
|
145
145
|
}
|
|
@@ -176,7 +176,7 @@ function KnowledgeDocEditorBody({ document, editorWidth = 'auto', kbName, proper
|
|
|
176
176
|
await saver(newTitle)
|
|
177
177
|
onDocTitleChanged()
|
|
178
178
|
} catch (e) {
|
|
179
|
-
console.error('Title update failed:', e)
|
|
179
|
+
console.error('Title update failed:', e instanceof Error ? e.message : String(e))
|
|
180
180
|
}
|
|
181
181
|
}, [onDocTitleChanged, saveHandlers])
|
|
182
182
|
|
|
@@ -186,7 +186,7 @@ function KnowledgeDocEditorBody({ document, editorWidth = 'auto', kbName, proper
|
|
|
186
186
|
const saver = saveHandlers?.tags ?? ((t: string[]) => updateDocumentFn({ data: { id: docId.current, tags: t } }))
|
|
187
187
|
await saver(newTags)
|
|
188
188
|
} catch (e) {
|
|
189
|
-
console.error('Tags update failed:', e)
|
|
189
|
+
console.error('Tags update failed:', e instanceof Error ? e.message : String(e))
|
|
190
190
|
}
|
|
191
191
|
}, [saveHandlers])
|
|
192
192
|
|
|
@@ -198,7 +198,7 @@ function KnowledgeDocEditorBody({ document, editorWidth = 'auto', kbName, proper
|
|
|
198
198
|
() => {
|
|
199
199
|
if (saveHandlers?.properties && latestPropertyValues.current) {
|
|
200
200
|
saveHandlers.properties(latestPropertyValues.current)
|
|
201
|
-
.catch((e) => console.error('Properties update failed:', e))
|
|
201
|
+
.catch((e) => console.error('Properties update failed:', e instanceof Error ? e.message : String(e)))
|
|
202
202
|
}
|
|
203
203
|
},
|
|
204
204
|
{ wait: EDITOR_AUTO_SAVE_MS },
|
|
@@ -66,7 +66,7 @@ export function KnowledgeSettingsPanel({ open, onOpenChange, kb, onUpdate }: Kno
|
|
|
66
66
|
await updateKnowledgeFn({ data: { id: kb.id, visibility: v } })
|
|
67
67
|
onUpdate?.()
|
|
68
68
|
} catch (e) {
|
|
69
|
-
console.error('Update visibility failed:', e)
|
|
69
|
+
console.error('Update visibility failed:', e instanceof Error ? e.message : String(e))
|
|
70
70
|
}
|
|
71
71
|
setSaving(false)
|
|
72
72
|
}
|
|
@@ -78,7 +78,7 @@ export function KnowledgeSettingsPanel({ open, onOpenChange, kb, onUpdate }: Kno
|
|
|
78
78
|
onOpenChange(false)
|
|
79
79
|
navigate({ to: '/knowledge' })
|
|
80
80
|
} catch (e) {
|
|
81
|
-
console.error('Delete failed:', e)
|
|
81
|
+
console.error('Delete failed:', e instanceof Error ? e.message : String(e))
|
|
82
82
|
}
|
|
83
83
|
setDeleting(false)
|
|
84
84
|
}
|
|
@@ -127,7 +127,7 @@ export function KnowledgeToolbar({ kb, onUpdate, breadcrumb, docType, treeCollap
|
|
|
127
127
|
await updateKnowledgeFn({ data: { id: kb.id, name: name.trim() } })
|
|
128
128
|
onUpdate?.()
|
|
129
129
|
} catch (e) {
|
|
130
|
-
console.error('Rename failed:', e)
|
|
130
|
+
console.error('Rename failed:', e instanceof Error ? e.message : String(e))
|
|
131
131
|
}
|
|
132
132
|
setEditingName(false)
|
|
133
133
|
}
|
|
@@ -137,7 +137,7 @@ export function KnowledgeToolbar({ kb, onUpdate, breadcrumb, docType, treeCollap
|
|
|
137
137
|
try {
|
|
138
138
|
await updateKnowledgeFn({ data: { id: kb.id, tags } })
|
|
139
139
|
} catch (e) {
|
|
140
|
-
console.error('Update tags failed:', e)
|
|
140
|
+
console.error('Update tags failed:', e instanceof Error ? e.message : String(e))
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -148,7 +148,7 @@ export function KnowledgeToolbar({ kb, onUpdate, breadcrumb, docType, treeCollap
|
|
|
148
148
|
await updateKnowledgeFn({ data: { id: kb.id, visibility: value as 'private' | 'team' } })
|
|
149
149
|
onUpdate?.()
|
|
150
150
|
} catch (e) {
|
|
151
|
-
console.error('Update visibility failed:', e)
|
|
151
|
+
console.error('Update visibility failed:', e instanceof Error ? e.message : String(e))
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -161,7 +161,7 @@ export function KnowledgeToolbar({ kb, onUpdate, breadcrumb, docType, treeCollap
|
|
|
161
161
|
window.dispatchEvent(new CustomEvent("knowledge-deleted"))
|
|
162
162
|
navigate({ to: '/knowledge' })
|
|
163
163
|
} catch (e) {
|
|
164
|
-
console.error('Delete failed:', e)
|
|
164
|
+
console.error('Delete failed:', e instanceof Error ? e.message : String(e))
|
|
165
165
|
}
|
|
166
166
|
setDeleting(false)
|
|
167
167
|
}
|
|
@@ -120,7 +120,7 @@ export function KnowledgeTreeItem({
|
|
|
120
120
|
onRefresh()
|
|
121
121
|
onDocRenamed?.(node.id)
|
|
122
122
|
} catch (e) {
|
|
123
|
-
console.error('Rename failed:', e)
|
|
123
|
+
console.error('Rename failed:', e instanceof Error ? e.message : String(e))
|
|
124
124
|
}
|
|
125
125
|
finishEditing()
|
|
126
126
|
}
|
|
@@ -134,7 +134,7 @@ export function KnowledgeTreeItem({
|
|
|
134
134
|
setDeleteConfirm('')
|
|
135
135
|
onRefresh()
|
|
136
136
|
} catch (e) {
|
|
137
|
-
console.error('Delete failed:', e)
|
|
137
|
+
console.error('Delete failed:', e instanceof Error ? e.message : String(e))
|
|
138
138
|
}
|
|
139
139
|
setDeleting(false)
|
|
140
140
|
}
|
|
@@ -156,7 +156,7 @@ export function KnowledgeTreeItem({
|
|
|
156
156
|
onRefresh()
|
|
157
157
|
onSelect(doc.id)
|
|
158
158
|
} catch (e) {
|
|
159
|
-
console.error('Create child failed:', e)
|
|
159
|
+
console.error('Create child failed:', e instanceof Error ? e.message : String(e))
|
|
160
160
|
}
|
|
161
161
|
setCreatingChild(false)
|
|
162
162
|
}
|
|
@@ -107,7 +107,7 @@ export function KnowledgeTree({
|
|
|
107
107
|
const docs = await listDocumentsFn({ data: { kbId } })
|
|
108
108
|
setDocuments(docs as TreeNode[])
|
|
109
109
|
} catch (e) {
|
|
110
|
-
console.error('Failed to fetch documents:', e)
|
|
110
|
+
console.error('Failed to fetch documents:', e instanceof Error ? e.message : String(e))
|
|
111
111
|
} finally {
|
|
112
112
|
setLoading(false)
|
|
113
113
|
}
|
|
@@ -155,7 +155,7 @@ export function KnowledgeTree({
|
|
|
155
155
|
await fetchDocuments()
|
|
156
156
|
onSelect(doc.id)
|
|
157
157
|
} catch (e) {
|
|
158
|
-
console.error('Create failed:', e)
|
|
158
|
+
console.error('Create failed:', e instanceof Error ? e.message : String(e))
|
|
159
159
|
}
|
|
160
160
|
setCreatingType(null)
|
|
161
161
|
}
|
|
@@ -16,11 +16,16 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { useTranslation } from "react-i18next"
|
|
19
|
-
import { Search } from "lucide-react"
|
|
19
|
+
import { Search, Command } from "lucide-react"
|
|
20
20
|
import { useSidebar } from "@/components/ui/sidebar"
|
|
21
21
|
import { cn } from "@/lib/utils"
|
|
22
22
|
import { setCommandPaletteOpen } from "@/lib/commands/command-palette-state"
|
|
23
23
|
|
|
24
|
+
const isMac =
|
|
25
|
+
typeof navigator !== "undefined" &&
|
|
26
|
+
(navigator.platform.toUpperCase().includes("MAC") ||
|
|
27
|
+
navigator.userAgent.toUpperCase().includes("MAC"))
|
|
28
|
+
|
|
24
29
|
export function SidebarSearchTrigger() {
|
|
25
30
|
const { t } = useTranslation("common")
|
|
26
31
|
const { state } = useSidebar()
|
|
@@ -50,14 +55,23 @@ export function SidebarSearchTrigger() {
|
|
|
50
55
|
)}
|
|
51
56
|
>
|
|
52
57
|
<Search className="size-4 shrink-0" />
|
|
53
|
-
<span className="flex-1 truncate text-left">
|
|
58
|
+
<span className="flex-1 truncate text-left text-xs">
|
|
54
59
|
{t("command.searchPlaceholder")}
|
|
55
60
|
</span>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
<span className="inline-flex items-center gap-1 rounded border border-border bg-muted/60 px-1.5 py-0.5">
|
|
62
|
+
<kbd
|
|
63
|
+
data-slot="kbd"
|
|
64
|
+
className="inline-flex items-center justify-center"
|
|
65
|
+
>
|
|
66
|
+
{isMac ? <Command size={10} /> : <span className="text-[10px] font-medium text-muted-foreground">Ctrl</span>}
|
|
67
|
+
</kbd>
|
|
68
|
+
<kbd
|
|
69
|
+
data-slot="kbd"
|
|
70
|
+
className="inline-flex items-center justify-center"
|
|
71
|
+
>
|
|
72
|
+
<span className="text-[11px] font-medium text-muted-foreground">K</span>
|
|
73
|
+
</kbd>
|
|
74
|
+
</span>
|
|
61
75
|
</button>
|
|
62
76
|
)
|
|
63
77
|
}
|
|
@@ -67,7 +67,7 @@ export default function AccountClient({
|
|
|
67
67
|
old ? { ...old, user: { ...old.user, avatarConfig: config, bgShape: shape, bgColor: color } } : old,
|
|
68
68
|
)
|
|
69
69
|
} catch (err) {
|
|
70
|
-
console.error("Failed to persist avatar:", err)
|
|
70
|
+
console.error("Failed to persist avatar:", err instanceof Error ? err.message : String(err))
|
|
71
71
|
toast.error(t("profile.avatarSaveFailed"))
|
|
72
72
|
}
|
|
73
73
|
}, [queryClient])
|