@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.
Files changed (43) hide show
  1. package/package.json +11 -8
  2. package/src/components/chat/ai-chat-history/components/conversation-item.tsx +1 -1
  3. package/src/components/chat/ai-chat-history/index.tsx +1 -1
  4. package/src/components/{command-palette.tsx → command/command-palette.tsx} +95 -47
  5. package/src/components/hextaui/command-menu.tsx +460 -0
  6. package/src/components/knowledge/editor/knowledge-doc-editor.tsx +4 -4
  7. package/src/components/knowledge/knowledge-settings-panel.tsx +2 -2
  8. package/src/components/knowledge/knowledge-toolbar.tsx +4 -4
  9. package/src/components/knowledge/knowledge-tree-item.tsx +3 -3
  10. package/src/components/knowledge/knowledge-tree.tsx +2 -2
  11. package/src/components/layout/sidebar-03/sidebar-search-trigger.tsx +21 -7
  12. package/src/components/settings/personal-team/account/account-client.tsx +1 -1
  13. package/src/components/story/detail/activity-timeline.tsx +7 -6
  14. package/src/lib/anyhub/manager.ts +17 -4
  15. package/src/lib/auth/auth.config.ts +2 -2
  16. package/src/lib/auth/sso-authorize.ts +1 -1
  17. package/src/lib/commands/registry.ts +2 -2
  18. package/src/lib/sanitize.ts +5 -0
  19. package/src/routes/_nav/chat/route.lazy.tsx +1 -1
  20. package/src/routes/_nav/design/index.lazy.tsx +1 -1
  21. package/src/routes/_nav/route.lazy.tsx +1 -1
  22. package/src/routes/_nav/settings/ai/route.tsx +1 -1
  23. package/src/routes/api/design/upload.ts +9 -1
  24. package/src/routes/design-files/$projectId/$artboardId/$filename.ts +6 -2
  25. package/src/routes/server.ai-stream.ts +1 -1
  26. package/src/server/audit.ts +1 -1
  27. package/src/server/keepalive/middleware.ts +1 -1
  28. package/src/server/knowledge-public/github-sync.ts +21 -3
  29. package/src/server/middlewares/audit-recorder.ts +1 -1
  30. package/src/server/middlewares/rate-limit.ts +27 -0
  31. package/src/server/references/engine.ts +11 -8
  32. package/src/server/serverFns/auth/forgot-password.ts +3 -1
  33. package/src/server/serverFns/auth/invitation.ts +1 -0
  34. package/src/server/serverFns/auth/register.ts +2 -0
  35. package/src/server/serverFns/auth/reset-password.ts +2 -0
  36. package/src/server/serverFns/auth/session.ts +2 -1
  37. package/src/server/serverFns/billing.ts +1 -1
  38. package/src/server/serverFns/compliance.ts +3 -2
  39. package/src/server/serverFns/design/settings.ts +1 -0
  40. package/src/server/serverFns/team.ts +1 -1
  41. package/src/server/sso/tokens.ts +18 -3
  42. package/src/start.ts +2 -2
  43. 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
- <span className="inline-flex items-center gap-0.5">
57
- <kbd className="rounded border border-border bg-muted/60 px-1 py-0.5 text-[10px] font-medium text-muted-foreground">
58
- ⌘K
59
- </kbd>
60
- </span>
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])