@peers-app/peers-ui 0.7.39 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/command-palette/command-palette-ui.js +190 -35
- package/dist/command-palette/command-palette.js +0 -121
- package/dist/globals.d.ts +1 -1
- package/dist/mention-configs.js +1 -1
- package/dist/screens/console-logs/console-logs-list.js +45 -47
- package/dist/screens/contacts/contact-list.js +4 -1
- package/dist/screens/contacts/index.d.ts +2 -0
- package/dist/screens/contacts/index.js +2 -0
- package/dist/screens/contacts/user-connect.d.ts +2 -0
- package/dist/screens/contacts/user-connect.js +312 -0
- package/dist/screens/search/global-search.js +110 -36
- package/package.json +3 -3
- package/src/command-palette/command-palette-ui.tsx +245 -12
- package/src/command-palette/command-palette.ts +0 -121
- package/src/mention-configs.ts +1 -1
- package/src/screens/console-logs/console-logs-list.tsx +47 -56
- package/src/screens/contacts/contact-list.tsx +4 -0
- package/src/screens/contacts/index.ts +3 -1
- package/src/screens/contacts/user-connect.tsx +452 -0
- package/src/screens/search/global-search.tsx +126 -7
|
@@ -9,11 +9,27 @@ import {
|
|
|
9
9
|
isCommandPaletteOpen,
|
|
10
10
|
searchCommands
|
|
11
11
|
} from './command-palette';
|
|
12
|
+
import { allPackages } from '../ui-router/routes-loader';
|
|
13
|
+
import { systemPackage } from '../system-apps';
|
|
14
|
+
import { goToTabPath } from '../tabs-layout/tabs-state';
|
|
15
|
+
import { IAppNav, Messages, newid, getMe } from "@peers-app/peers-sdk";
|
|
16
|
+
import { openThreadInTab } from '../globals';
|
|
17
|
+
|
|
18
|
+
interface AppSearchItem {
|
|
19
|
+
packageId: string;
|
|
20
|
+
packageName: string;
|
|
21
|
+
navItem: IAppNav;
|
|
22
|
+
path: string;
|
|
23
|
+
name: string;
|
|
24
|
+
displayName: string;
|
|
25
|
+
iconClassName: string;
|
|
26
|
+
}
|
|
12
27
|
|
|
13
28
|
export function CommandPaletteOverlay() {
|
|
14
29
|
const [isOpen] = useObservable(isCommandPaletteOpen);
|
|
15
30
|
const [_persistedQuery] = useObservable(commandSearchQuery);
|
|
16
31
|
const [_colorMode] = useObservable(colorMode);
|
|
32
|
+
const [packages] = useObservable(allPackages);
|
|
17
33
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
18
34
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
19
35
|
|
|
@@ -53,7 +69,78 @@ export function CommandPaletteOverlay() {
|
|
|
53
69
|
const searchQuery = localQuery;
|
|
54
70
|
const filteredCommands = searchCommands(searchQuery);
|
|
55
71
|
|
|
72
|
+
// Get all apps (system and user)
|
|
73
|
+
const getAllApps = (): AppSearchItem[] => {
|
|
74
|
+
const allPackages_ = [...packages, systemPackage];
|
|
75
|
+
return allPackages_
|
|
76
|
+
.filter(p => !p.disabled && p.appNavs && p.appNavs.length > 0)
|
|
77
|
+
.flatMap(pkg =>
|
|
78
|
+
pkg.appNavs!.map(navItem => {
|
|
79
|
+
// Construct path - use direct path for system apps, package-nav for others
|
|
80
|
+
let path: string;
|
|
81
|
+
if (pkg.packageId === 'system-apps') {
|
|
82
|
+
path = navItem.navigationPath ?? navItem.name.replace(/\s/g, '-').toLowerCase();
|
|
83
|
+
} else {
|
|
84
|
+
path = `package-nav/${pkg.packageId}/${(navItem.navigationPath ?? navItem.name).replace(/[^a-zA-Z0-9]/g, '-').toLowerCase()}`;
|
|
85
|
+
while (path.includes('//')) {
|
|
86
|
+
path = path.replace('//', '/');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
packageId: pkg.packageId,
|
|
92
|
+
packageName: pkg.name,
|
|
93
|
+
navItem,
|
|
94
|
+
path,
|
|
95
|
+
name: navItem.name,
|
|
96
|
+
displayName: navItem.displayName || navItem.name,
|
|
97
|
+
iconClassName: navItem.iconClassName || 'bi-box-seam'
|
|
98
|
+
};
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Search apps
|
|
104
|
+
const searchApps = (query: string): AppSearchItem[] => {
|
|
105
|
+
if (!query.trim()) return [];
|
|
106
|
+
const allApps = getAllApps();
|
|
107
|
+
const lowerQuery = query.toLowerCase();
|
|
108
|
+
return allApps.filter(app =>
|
|
109
|
+
app.name.toLowerCase().includes(lowerQuery) ||
|
|
110
|
+
app.displayName.toLowerCase().includes(lowerQuery) ||
|
|
111
|
+
app.packageName.toLowerCase().includes(lowerQuery)
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const filteredApps = searchApps(searchQuery);
|
|
116
|
+
|
|
117
|
+
// Function to create a new thread from search query
|
|
118
|
+
const createNewThreadFromQuery = async (query: string) => {
|
|
119
|
+
try {
|
|
120
|
+
const currentUser = await getMe();
|
|
121
|
+
const threadMessage = await Messages().insert({
|
|
122
|
+
messageId: newid(),
|
|
123
|
+
userId: currentUser.userId,
|
|
124
|
+
channelId: currentUser.userId,
|
|
125
|
+
message: query.trim(),
|
|
126
|
+
createdAt: new Date(),
|
|
127
|
+
});
|
|
128
|
+
await openThreadInTab(threadMessage);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('Failed to create new thread:', error);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
56
134
|
// Create a flattened list that matches the visual rendering order
|
|
135
|
+
// Apps first, then commands
|
|
136
|
+
const allItems: Array<{ type: 'app' | 'command'; app?: AppSearchItem; command?: Command; id: string }> = [];
|
|
137
|
+
|
|
138
|
+
// Add apps first
|
|
139
|
+
filteredApps.forEach((app) => {
|
|
140
|
+
allItems.push({ type: 'app', app, id: `app-${app.packageId}-${app.path}` });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Then add commands
|
|
57
144
|
const visualOrderCommands = Object.entries(
|
|
58
145
|
filteredCommands.reduce((acc, cmd) => {
|
|
59
146
|
const category = cmd.category || 'Other';
|
|
@@ -63,6 +150,10 @@ export function CommandPaletteOverlay() {
|
|
|
63
150
|
}, {} as Record<string, Command[]>)
|
|
64
151
|
).flatMap(([, commands]) => commands);
|
|
65
152
|
|
|
153
|
+
visualOrderCommands.forEach((cmd) => {
|
|
154
|
+
allItems.push({ type: 'command', command: cmd, id: `command-${cmd.id}` });
|
|
155
|
+
});
|
|
156
|
+
|
|
66
157
|
// Focus input when opened
|
|
67
158
|
useEffect(() => {
|
|
68
159
|
if (isOpen && inputRef.current) {
|
|
@@ -82,21 +173,40 @@ export function CommandPaletteOverlay() {
|
|
|
82
173
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
83
174
|
if (e.key === 'ArrowDown') {
|
|
84
175
|
e.preventDefault();
|
|
85
|
-
|
|
176
|
+
// Allow selection to go one past the end if there's a search query (for new thread option)
|
|
177
|
+
const maxIndex = searchQuery.trim() ? allItems.length : allItems.length - 1;
|
|
178
|
+
setSelectedIndex(prev => Math.min(prev + 1, maxIndex));
|
|
86
179
|
} else if (e.key === 'ArrowUp') {
|
|
87
180
|
e.preventDefault();
|
|
88
181
|
setSelectedIndex(prev => Math.max(prev - 1, 0));
|
|
89
182
|
} else if (e.key === 'Enter') {
|
|
90
183
|
e.preventDefault();
|
|
91
|
-
if (
|
|
92
|
-
|
|
184
|
+
// Check if the new thread option is selected (selectedIndex >= allItems.length)
|
|
185
|
+
if (selectedIndex >= allItems.length && searchQuery.trim()) {
|
|
186
|
+
closeCommandPalette();
|
|
187
|
+
createNewThreadFromQuery(searchQuery);
|
|
188
|
+
} else {
|
|
189
|
+
const selectedItem = allItems[selectedIndex];
|
|
190
|
+
if (selectedItem) {
|
|
191
|
+
if (selectedItem.type === 'app' && selectedItem.app) {
|
|
192
|
+
closeCommandPalette();
|
|
193
|
+
goToTabPath(selectedItem.app.path);
|
|
194
|
+
} else if (selectedItem.type === 'command' && selectedItem.command) {
|
|
195
|
+
executeCommand(selectedItem.command.id);
|
|
196
|
+
}
|
|
197
|
+
} else if (searchQuery.trim()) {
|
|
198
|
+
// Fallback: Create a new thread with the search query as the message
|
|
199
|
+
// This happens when no item is selected (selectedIndex is out of bounds or -1)
|
|
200
|
+
closeCommandPalette();
|
|
201
|
+
createNewThreadFromQuery(searchQuery);
|
|
202
|
+
}
|
|
93
203
|
}
|
|
94
204
|
}
|
|
95
205
|
};
|
|
96
206
|
|
|
97
207
|
document.addEventListener('keydown', handleKeyDown);
|
|
98
208
|
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
99
|
-
}, [isOpen,
|
|
209
|
+
}, [isOpen, allItems, selectedIndex]);
|
|
100
210
|
|
|
101
211
|
if (!isOpen) return null;
|
|
102
212
|
|
|
@@ -151,7 +261,7 @@ export function CommandPaletteOverlay() {
|
|
|
151
261
|
ref={inputRef}
|
|
152
262
|
type="text"
|
|
153
263
|
className={`form-control ${isDark ? 'bg-dark text-light border-secondary' : ''}`}
|
|
154
|
-
placeholder="
|
|
264
|
+
placeholder="Search apps, commands, or navigate..."
|
|
155
265
|
value={searchQuery}
|
|
156
266
|
onChange={(e) => {
|
|
157
267
|
const newQuery = e.target.value;
|
|
@@ -178,22 +288,85 @@ export function CommandPaletteOverlay() {
|
|
|
178
288
|
</div>
|
|
179
289
|
</div>
|
|
180
290
|
|
|
181
|
-
{/* Commands List */}
|
|
291
|
+
{/* Commands and Apps List */}
|
|
182
292
|
<div
|
|
183
293
|
style={{
|
|
184
294
|
maxHeight: '400px',
|
|
185
295
|
overflowY: 'auto'
|
|
186
296
|
}}
|
|
187
297
|
>
|
|
188
|
-
{
|
|
298
|
+
{allItems.length === 0 && !searchQuery.trim() ? (
|
|
189
299
|
<div className="p-4 text-center text-muted">
|
|
190
300
|
<i className="bi-search mb-2 d-block" style={{ fontSize: '24px' }} />
|
|
191
|
-
No
|
|
301
|
+
No results found
|
|
192
302
|
</div>
|
|
193
303
|
) : (
|
|
194
304
|
<div className="py-2">
|
|
195
|
-
{/*
|
|
196
|
-
{
|
|
305
|
+
{/* Show "No results" message if there's a query but no results yet */}
|
|
306
|
+
{allItems.length === 0 && searchQuery.trim() && (
|
|
307
|
+
<div className="px-3 py-2 text-muted small text-center">
|
|
308
|
+
No matching results
|
|
309
|
+
</div>
|
|
310
|
+
)}
|
|
311
|
+
{/* Apps Section */}
|
|
312
|
+
{filteredApps.length > 0 && (
|
|
313
|
+
<div>
|
|
314
|
+
<div
|
|
315
|
+
className="px-3 py-1 small text-muted fw-bold text-uppercase"
|
|
316
|
+
style={{ fontSize: '11px', letterSpacing: '0.5px' }}
|
|
317
|
+
>
|
|
318
|
+
Apps
|
|
319
|
+
</div>
|
|
320
|
+
{filteredApps.map((app) => {
|
|
321
|
+
const appId = `app-${app.packageId}-${app.path}`;
|
|
322
|
+
const globalIndex = allItems.findIndex(item => item.id === appId);
|
|
323
|
+
const isSelected = globalIndex === selectedIndex;
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
<div
|
|
327
|
+
key={`${app.packageId}-${app.path}`}
|
|
328
|
+
className={`px-3 py-2 d-flex align-items-center justify-content-between ${
|
|
329
|
+
isSelected
|
|
330
|
+
? (isDark ? 'bg-primary bg-opacity-25' : 'bg-primary bg-opacity-10')
|
|
331
|
+
: ''
|
|
332
|
+
}`}
|
|
333
|
+
style={{
|
|
334
|
+
cursor: 'pointer',
|
|
335
|
+
transition: 'background-color 0.1s ease'
|
|
336
|
+
}}
|
|
337
|
+
onClick={() => {
|
|
338
|
+
closeCommandPalette();
|
|
339
|
+
goToTabPath(app.path);
|
|
340
|
+
}}
|
|
341
|
+
onMouseEnter={() => setSelectedIndex(globalIndex)}
|
|
342
|
+
>
|
|
343
|
+
<div className="d-flex align-items-center">
|
|
344
|
+
<i
|
|
345
|
+
className={`${app.iconClassName} me-3`}
|
|
346
|
+
style={{
|
|
347
|
+
fontSize: '16px',
|
|
348
|
+
color: isSelected ? (isDark ? '#ffffff' : '#0d6efd') : '#6c757d',
|
|
349
|
+
minWidth: '16px'
|
|
350
|
+
}}
|
|
351
|
+
/>
|
|
352
|
+
<div>
|
|
353
|
+
<div className="fw-medium">{app.displayName}</div>
|
|
354
|
+
<div
|
|
355
|
+
className="small text-muted"
|
|
356
|
+
style={{ fontSize: '12px' }}
|
|
357
|
+
>
|
|
358
|
+
{app.packageId === 'system-apps' ? 'System App' : app.packageName}
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
);
|
|
364
|
+
})}
|
|
365
|
+
</div>
|
|
366
|
+
)}
|
|
367
|
+
|
|
368
|
+
{/* Commands Section - Group commands by category */}
|
|
369
|
+
{filteredCommands.length > 0 && Object.entries(
|
|
197
370
|
filteredCommands.reduce((acc, cmd) => {
|
|
198
371
|
const category = cmd.category || 'Other';
|
|
199
372
|
if (!acc[category]) acc[category] = [];
|
|
@@ -212,7 +385,8 @@ export function CommandPaletteOverlay() {
|
|
|
212
385
|
|
|
213
386
|
{/* Commands in Category */}
|
|
214
387
|
{commands.map((command) => {
|
|
215
|
-
const
|
|
388
|
+
const commandId = `command-${command.id}`;
|
|
389
|
+
const globalIndex = allItems.findIndex(item => item.id === commandId);
|
|
216
390
|
const isSelected = globalIndex === selectedIndex;
|
|
217
391
|
|
|
218
392
|
return (
|
|
@@ -274,6 +448,64 @@ export function CommandPaletteOverlay() {
|
|
|
274
448
|
})}
|
|
275
449
|
</div>
|
|
276
450
|
))}
|
|
451
|
+
|
|
452
|
+
{/* New Thread Indicator - Show when there's a search query */}
|
|
453
|
+
{searchQuery.trim() && (
|
|
454
|
+
<div>
|
|
455
|
+
<div
|
|
456
|
+
className="px-3 py-1 small text-muted fw-bold text-uppercase"
|
|
457
|
+
style={{ fontSize: '11px', letterSpacing: '0.5px' }}
|
|
458
|
+
>
|
|
459
|
+
Actions
|
|
460
|
+
</div>
|
|
461
|
+
<div
|
|
462
|
+
className={`px-3 py-2 d-flex align-items-center ${
|
|
463
|
+
(allItems.length === 0 || selectedIndex >= allItems.length) && searchQuery.trim()
|
|
464
|
+
? (isDark ? 'bg-primary bg-opacity-25' : 'bg-primary bg-opacity-10')
|
|
465
|
+
: ''
|
|
466
|
+
}`}
|
|
467
|
+
style={{
|
|
468
|
+
cursor: 'pointer',
|
|
469
|
+
transition: 'background-color 0.1s ease',
|
|
470
|
+
borderTop: allItems.length > 0 ? `1px solid ${isDark ? '#495057' : '#dee2e6'}` : 'none',
|
|
471
|
+
marginTop: allItems.length > 0 ? '8px' : '0',
|
|
472
|
+
paddingTop: allItems.length > 0 ? '12px' : '8px'
|
|
473
|
+
}}
|
|
474
|
+
onClick={() => {
|
|
475
|
+
closeCommandPalette();
|
|
476
|
+
createNewThreadFromQuery(searchQuery);
|
|
477
|
+
}}
|
|
478
|
+
onMouseEnter={() => {
|
|
479
|
+
// Set selected index beyond the list to indicate this item is selected
|
|
480
|
+
setSelectedIndex(allItems.length);
|
|
481
|
+
}}
|
|
482
|
+
>
|
|
483
|
+
<div className="d-flex align-items-center">
|
|
484
|
+
<i
|
|
485
|
+
className="bi-chat-dots me-3"
|
|
486
|
+
style={{
|
|
487
|
+
fontSize: '16px',
|
|
488
|
+
color: (allItems.length === 0 || selectedIndex >= allItems.length) && searchQuery.trim()
|
|
489
|
+
? (isDark ? '#ffffff' : '#0d6efd')
|
|
490
|
+
: '#6c757d',
|
|
491
|
+
minWidth: '16px'
|
|
492
|
+
}}
|
|
493
|
+
/>
|
|
494
|
+
<div>
|
|
495
|
+
<div className="fw-medium">
|
|
496
|
+
Start new thread: <span className="text-muted">{searchQuery.trim().slice(0, 50)}{searchQuery.trim().length > 50 ? '...' : ''}</span>
|
|
497
|
+
</div>
|
|
498
|
+
<div
|
|
499
|
+
className="small text-muted"
|
|
500
|
+
style={{ fontSize: '12px' }}
|
|
501
|
+
>
|
|
502
|
+
Press Enter to create
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
</div>
|
|
508
|
+
)}
|
|
277
509
|
</div>
|
|
278
510
|
)}
|
|
279
511
|
</div>
|
|
@@ -298,7 +530,8 @@ export function CommandPaletteOverlay() {
|
|
|
298
530
|
</span>
|
|
299
531
|
</div>
|
|
300
532
|
<div>
|
|
301
|
-
{
|
|
533
|
+
{allItems.length} result{allItems.length !== 1 ? 's' : ''}
|
|
534
|
+
{searchQuery.trim() && allItems.length === 0 && ' • Start thread'}
|
|
302
535
|
</div>
|
|
303
536
|
</div>
|
|
304
537
|
</div>
|
|
@@ -98,127 +98,6 @@ const coreCommands: Command[] = [
|
|
|
98
98
|
closeCommandPalette();
|
|
99
99
|
goToTabPath('threads');
|
|
100
100
|
}
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
id: 'go-settings',
|
|
104
|
-
label: 'Open Settings',
|
|
105
|
-
description: 'Open application settings',
|
|
106
|
-
iconClassName: 'bi-gear-fill',
|
|
107
|
-
category: 'Navigation',
|
|
108
|
-
action: () => {
|
|
109
|
-
closeCommandPalette();
|
|
110
|
-
goToTabPath('settings');
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
id: 'go-assistants',
|
|
115
|
-
label: 'Go to Assistants',
|
|
116
|
-
description: 'View and manage assistants',
|
|
117
|
-
iconClassName: 'bi-person-fill-gear',
|
|
118
|
-
category: 'Navigation',
|
|
119
|
-
action: () => {
|
|
120
|
-
closeCommandPalette();
|
|
121
|
-
goToTabPath('assistants');
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
id: 'go-workflows',
|
|
126
|
-
label: 'Go to Workflows',
|
|
127
|
-
description: 'View and manage workflows',
|
|
128
|
-
iconClassName: 'bi-database-fill-gear',
|
|
129
|
-
category: 'Navigation',
|
|
130
|
-
action: () => {
|
|
131
|
-
closeCommandPalette();
|
|
132
|
-
goToTabPath('workflows');
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
id: 'go-tools',
|
|
137
|
-
label: 'Go to Tools',
|
|
138
|
-
description: 'View and manage tools',
|
|
139
|
-
iconClassName: 'bi-tools',
|
|
140
|
-
category: 'Navigation',
|
|
141
|
-
action: () => {
|
|
142
|
-
closeCommandPalette();
|
|
143
|
-
goToTabPath('tools');
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
id: 'go-events',
|
|
148
|
-
label: 'Go to Events',
|
|
149
|
-
description: 'View and manage events',
|
|
150
|
-
iconClassName: 'bi-lightning-charge-fill',
|
|
151
|
-
category: 'Navigation',
|
|
152
|
-
action: () => {
|
|
153
|
-
closeCommandPalette();
|
|
154
|
-
goToTabPath('events');
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
id: 'go-predicates',
|
|
159
|
-
label: 'Go to Predicates',
|
|
160
|
-
description: 'View and manage predicates',
|
|
161
|
-
iconClassName: 'bi-node-plus-fill',
|
|
162
|
-
category: 'Navigation',
|
|
163
|
-
action: () => {
|
|
164
|
-
closeCommandPalette();
|
|
165
|
-
goToTabPath('predicates');
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
id: 'go-peer-types',
|
|
170
|
-
label: 'Go to Peer Types',
|
|
171
|
-
description: 'View and manage peer types',
|
|
172
|
-
iconClassName: 'bi-code-square',
|
|
173
|
-
category: 'Navigation',
|
|
174
|
-
action: () => {
|
|
175
|
-
closeCommandPalette();
|
|
176
|
-
goToTabPath('peer-types');
|
|
177
|
-
}
|
|
178
|
-
},
|
|
179
|
-
{
|
|
180
|
-
id: 'go-packages',
|
|
181
|
-
label: 'Go to Packages',
|
|
182
|
-
description: 'View and manage packages',
|
|
183
|
-
iconClassName: 'bi-box-fill',
|
|
184
|
-
category: 'Navigation',
|
|
185
|
-
action: () => {
|
|
186
|
-
closeCommandPalette();
|
|
187
|
-
goToTabPath('packages');
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
id: 'go-variables',
|
|
192
|
-
label: 'Go to Variables',
|
|
193
|
-
description: 'View and manage variables',
|
|
194
|
-
iconClassName: 'bi-braces',
|
|
195
|
-
category: 'Navigation',
|
|
196
|
-
action: () => {
|
|
197
|
-
closeCommandPalette();
|
|
198
|
-
goToTabPath('variables');
|
|
199
|
-
}
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
id: 'go-knowledge-values',
|
|
203
|
-
label: 'Go to Knowledge Values',
|
|
204
|
-
description: 'View and manage knowledge values',
|
|
205
|
-
iconClassName: 'bi-journal-bookmark-fill',
|
|
206
|
-
category: 'Navigation',
|
|
207
|
-
action: () => {
|
|
208
|
-
closeCommandPalette();
|
|
209
|
-
goToTabPath('knowledge-values');
|
|
210
|
-
}
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
id: 'go-knowledge-frames',
|
|
214
|
-
label: 'Go to Knowledge Frames',
|
|
215
|
-
description: 'View and manage knowledge frames',
|
|
216
|
-
iconClassName: 'bi-window-dock',
|
|
217
|
-
category: 'Navigation',
|
|
218
|
-
action: () => {
|
|
219
|
-
closeCommandPalette();
|
|
220
|
-
goToTabPath('knowledge-frames');
|
|
221
|
-
}
|
|
222
101
|
}
|
|
223
102
|
];
|
|
224
103
|
|
package/src/mention-configs.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ConsoleLogs, DataFilter, IConsoleLog } from "@peers-app/peers-sdk";
|
|
1
|
+
import { ConsoleLogs, DataFilter, IConsoleLog, ISubscriptionResult, newid, sleep } from "@peers-app/peers-sdk";
|
|
2
2
|
import { min, sortBy, uniqBy } from 'lodash';
|
|
3
3
|
import React, { Fragment, useEffect, useMemo, useState } from 'react';
|
|
4
4
|
import InfiniteScroll from 'react-infinite-scroll-component';
|
|
@@ -29,14 +29,15 @@ const DEFAULT_COLUMNS: Column[] = [
|
|
|
29
29
|
export const ConsoleLogsList = () => {
|
|
30
30
|
const logs = useObservableState<IConsoleLog[]>([]);
|
|
31
31
|
const [allLogsLoaded, setAllLogsLoaded] = useState(false);
|
|
32
|
+
const loadMoreId = useObservableState<string>(newid(), true);
|
|
32
33
|
const [levelFilter, setLevelFilter] = useState<string>('all');
|
|
33
34
|
const [processFilter, setProcessFilter] = useState<string>('all');
|
|
34
|
-
const
|
|
35
|
+
const searchText = useObservableState<string>('');
|
|
35
36
|
const [columns, setColumns] = useState<Column[]>(DEFAULT_COLUMNS);
|
|
36
37
|
const [totalLogCount, setTotalLogCount] = useState<number>(0);
|
|
37
38
|
const [_colorMode] = useObservable(colorMode);
|
|
38
39
|
const logsEndRef = React.useRef<HTMLDivElement>(null);
|
|
39
|
-
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
40
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
40
41
|
|
|
41
42
|
const batchSize = 50;
|
|
42
43
|
|
|
@@ -82,6 +83,9 @@ export const ConsoleLogsList = () => {
|
|
|
82
83
|
if (processFilter !== 'all') {
|
|
83
84
|
filter.process = processFilter;
|
|
84
85
|
}
|
|
86
|
+
if (searchText()) {
|
|
87
|
+
filter.message = { $matchWords: searchText() }
|
|
88
|
+
}
|
|
85
89
|
return filter;
|
|
86
90
|
};
|
|
87
91
|
|
|
@@ -90,15 +94,6 @@ export const ConsoleLogsList = () => {
|
|
|
90
94
|
const table = await ConsoleLogs();
|
|
91
95
|
const filter = buildFilter();
|
|
92
96
|
let count = await table.count(filter);
|
|
93
|
-
|
|
94
|
-
// If search text is applied, we need to count manually since it's client-side filtering
|
|
95
|
-
if (searchText) {
|
|
96
|
-
const allLogs = await table.list(filter);
|
|
97
|
-
count = allLogs.filter(log =>
|
|
98
|
-
log.message.toLowerCase().includes(searchText.toLowerCase())
|
|
99
|
-
).length;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
97
|
setTotalLogCount(count);
|
|
103
98
|
}
|
|
104
99
|
|
|
@@ -106,63 +101,57 @@ export const ConsoleLogsList = () => {
|
|
|
106
101
|
async function fetchLogs(lastLog?: IConsoleLog): Promise<IConsoleLog[]> {
|
|
107
102
|
const table = await ConsoleLogs();
|
|
108
103
|
const filter: any = buildFilter();
|
|
109
|
-
|
|
110
104
|
if (lastLog) {
|
|
111
105
|
filter.logId = { $lt: lastLog.logId };
|
|
112
106
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
sortBy: ['-timestamp', '-logId'],
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const fetchedLogs: IConsoleLog[] = [];
|
|
119
|
-
for await (const log of cursor) {
|
|
120
|
-
// Apply text search filter (if search is implemented in cursor, this can be removed)
|
|
121
|
-
if (searchText && !log.message.toLowerCase().includes(searchText.toLowerCase())) {
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
fetchedLogs.push(log);
|
|
125
|
-
if (fetchedLogs.length >= batchSize) {
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return fetchedLogs;
|
|
107
|
+
const results = await table.list(filter, { pageSize: batchSize, sortBy: ['-timestamp', '-logId'] });
|
|
108
|
+
return results;
|
|
130
109
|
}
|
|
131
110
|
|
|
132
111
|
// Load older logs (prepend to list)
|
|
133
|
-
function
|
|
112
|
+
async function loadMoreLogs(startLoadId?: string) {
|
|
113
|
+
if (startLoadId && startLoadId !== loadMoreId()) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
startLoadId ??= loadMoreId();
|
|
134
117
|
const oldestLog = logs()[0];
|
|
135
|
-
fetchLogs(oldestLog)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
118
|
+
const fetchedLogs = await fetchLogs(oldestLog);
|
|
119
|
+
if (loadMoreId() !== startLoadId) {
|
|
120
|
+
loadMoreLogs(loadMoreId());
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (fetchedLogs.length === 0) {
|
|
124
|
+
setAllLogsLoaded(true);
|
|
125
|
+
}
|
|
126
|
+
let _logs = sortBy([...logs(), ...fetchedLogs], 'timestamp');
|
|
127
|
+
_logs = uniqBy(_logs, l => l.logId);
|
|
128
|
+
logs(_logs);
|
|
145
129
|
}
|
|
146
130
|
|
|
147
131
|
// Initial load and ensure screen is filled
|
|
148
132
|
const minHeightOfLog = 30;
|
|
149
133
|
useEffect(() => {
|
|
150
134
|
if (!allLogsLoaded && (!logs.length || logs.length * minHeightOfLog < windowHeight())) {
|
|
151
|
-
|
|
135
|
+
loadMoreLogs();
|
|
152
136
|
}
|
|
153
|
-
}, [logs, levelFilter, processFilter, searchText]);
|
|
137
|
+
}, [logs, levelFilter, processFilter, searchText()]);
|
|
154
138
|
|
|
155
139
|
// Reset when filters change
|
|
156
140
|
useEffect(() => {
|
|
141
|
+
loadMoreId(newid());
|
|
157
142
|
logs([]);
|
|
158
|
-
setAllLogsLoaded(false);
|
|
159
143
|
updateLogCount();
|
|
160
|
-
|
|
144
|
+
if (allLogsLoaded) {
|
|
145
|
+
setAllLogsLoaded(false);
|
|
146
|
+
loadMoreLogs()
|
|
147
|
+
}
|
|
148
|
+
}, [levelFilter, processFilter, searchText()]);
|
|
161
149
|
|
|
162
150
|
// Subscribe to new logs
|
|
163
151
|
useEffect(() => {
|
|
152
|
+
let sub: ISubscriptionResult | undefined = undefined;
|
|
164
153
|
ConsoleLogs().then(table => {
|
|
165
|
-
|
|
154
|
+
sub = table.dataChanged.subscribe(evt => {
|
|
166
155
|
// Update count whenever data changes
|
|
167
156
|
updateLogCount();
|
|
168
157
|
|
|
@@ -171,7 +160,11 @@ export const ConsoleLogsList = () => {
|
|
|
171
160
|
// Check if log matches current filters
|
|
172
161
|
if (levelFilter !== 'all' && log.level !== levelFilter) return;
|
|
173
162
|
if (processFilter !== 'all' && log.process !== processFilter) return;
|
|
174
|
-
if (searchText
|
|
163
|
+
if (searchText()) {
|
|
164
|
+
const logMessage = log.message.toLowerCase();
|
|
165
|
+
const filterOut = searchText().toLowerCase().split(' ').some(word => !logMessage.includes(word));
|
|
166
|
+
if (filterOut) return;
|
|
167
|
+
}
|
|
175
168
|
|
|
176
169
|
// Don't add we're only showing a limited batch and this is older
|
|
177
170
|
if (logs().length > batchSize && min(logs().map(l => l.timestamp))! > log.timestamp) return;
|
|
@@ -183,12 +176,11 @@ export const ConsoleLogsList = () => {
|
|
|
183
176
|
scrollToBottom('smooth');
|
|
184
177
|
}
|
|
185
178
|
});
|
|
186
|
-
|
|
187
|
-
return () => {
|
|
188
|
-
sub.unsubscribe();
|
|
189
|
-
};
|
|
190
179
|
});
|
|
191
|
-
|
|
180
|
+
return () => {
|
|
181
|
+
sub?.unsubscribe();
|
|
182
|
+
};
|
|
183
|
+
}, [levelFilter, processFilter]);
|
|
192
184
|
|
|
193
185
|
function scrollToBottom(behavior: 'instant' | 'smooth', delay = 100) {
|
|
194
186
|
setTimeout(() => {
|
|
@@ -202,7 +194,6 @@ export const ConsoleLogsList = () => {
|
|
|
202
194
|
const table = await ConsoleLogs();
|
|
203
195
|
await table.deleteOldLogs(Date.now());
|
|
204
196
|
logs([]);
|
|
205
|
-
setAllLogsLoaded(true);
|
|
206
197
|
setTotalLogCount(0);
|
|
207
198
|
} catch (err) {
|
|
208
199
|
console.error('Failed to clear logs:', err);
|
|
@@ -219,8 +210,8 @@ export const ConsoleLogsList = () => {
|
|
|
219
210
|
setLevelFilter={setLevelFilter}
|
|
220
211
|
processFilter={processFilter}
|
|
221
212
|
setProcessFilter={setProcessFilter}
|
|
222
|
-
searchText={searchText}
|
|
223
|
-
setSearchText={
|
|
213
|
+
searchText={searchText()}
|
|
214
|
+
setSearchText={searchText}
|
|
224
215
|
/>
|
|
225
216
|
|
|
226
217
|
<div
|
|
@@ -254,7 +245,7 @@ export const ConsoleLogsList = () => {
|
|
|
254
245
|
>
|
|
255
246
|
<InfiniteScroll
|
|
256
247
|
dataLength={_logs.length}
|
|
257
|
-
next={
|
|
248
|
+
next={loadMoreLogs}
|
|
258
249
|
style={{ display: 'flex', flexDirection: 'column-reverse', overflow: 'hidden' }}
|
|
259
250
|
inverse={true}
|
|
260
251
|
hasMore={!allLogsLoaded}
|