@geminilight/mindos 0.6.22 → 0.6.25
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/README.md +58 -46
- package/README_zh.md +58 -46
- package/app/app/.well-known/agent-card.json/route.ts +34 -0
- package/app/app/api/a2a/route.ts +100 -0
- package/app/app/api/file/import/route.ts +0 -2
- package/app/app/api/setup/route.ts +2 -0
- package/app/components/Backlinks.tsx +2 -2
- package/app/components/Breadcrumb.tsx +1 -1
- package/app/components/CsvView.tsx +41 -19
- package/app/components/DirView.tsx +2 -2
- package/app/components/FileTree.tsx +14 -1
- package/app/components/GuideCard.tsx +6 -2
- package/app/components/HomeContent.tsx +2 -2
- package/app/components/RightAskPanel.tsx +17 -10
- package/app/components/SearchModal.tsx +3 -3
- package/app/components/SidebarLayout.tsx +4 -2
- package/app/components/SyncStatusBar.tsx +2 -2
- package/app/components/ask/AskContent.tsx +6 -6
- package/app/components/ask/FileChip.tsx +1 -1
- package/app/components/ask/MentionPopover.tsx +2 -2
- package/app/components/ask/MessageList.tsx +1 -1
- package/app/components/ask/SlashCommandPopover.tsx +1 -1
- package/app/components/explore/UseCaseCard.tsx +2 -2
- package/app/components/help/HelpContent.tsx +6 -1
- package/app/components/panels/AgentsPanelAgentDetail.tsx +2 -2
- package/app/components/panels/DiscoverPanel.tsx +3 -3
- package/app/components/panels/PanelNavRow.tsx +2 -2
- package/app/components/panels/PluginsPanel.tsx +1 -1
- package/app/components/panels/SearchPanel.tsx +3 -3
- package/app/components/renderers/summary/SummaryRenderer.tsx +1 -1
- package/app/components/settings/AiTab.tsx +4 -4
- package/app/components/settings/KnowledgeTab.tsx +1 -1
- package/app/components/settings/McpTab.tsx +22 -4
- package/app/components/settings/UpdateTab.tsx +1 -1
- package/app/components/setup/index.tsx +9 -3
- package/app/components/walkthrough/WalkthroughProvider.tsx +2 -2
- package/app/hooks/useAskPanel.ts +7 -3
- package/app/hooks/useFileImport.ts +1 -1
- package/app/lib/a2a/agent-card.ts +107 -0
- package/app/lib/a2a/index.ts +23 -0
- package/app/lib/a2a/task-handler.ts +228 -0
- package/app/lib/a2a/types.ts +158 -0
- package/app/lib/agent/tools.ts +1 -1
- package/app/lib/core/fs-ops.ts +3 -2
- package/app/lib/fs.ts +28 -11
- package/app/lib/i18n-en.ts +2 -0
- package/app/lib/i18n-zh.ts +2 -0
- package/app/lib/settings.ts +1 -1
- package/bin/cli.js +48 -20
- package/bin/commands/agent.js +18 -0
- package/bin/commands/api.js +58 -0
- package/bin/commands/ask.js +101 -0
- package/bin/commands/file.js +286 -0
- package/bin/commands/search.js +51 -0
- package/bin/commands/space.js +167 -0
- package/bin/commands/status.js +69 -0
- package/bin/lib/command.js +156 -0
- package/mcp/dist/index.cjs +1 -1
- package/mcp/src/index.ts +1 -1
- package/package.json +1 -1
- package/skills/mindos/SKILL.md +2 -2
- package/skills/mindos-zh/SKILL.md +2 -2
|
@@ -6,7 +6,7 @@ import { FileNode } from '@/lib/types';
|
|
|
6
6
|
import { encodePath } from '@/lib/utils';
|
|
7
7
|
import {
|
|
8
8
|
ChevronDown, FileText, Table, Folder, FolderOpen, Plus, Loader2,
|
|
9
|
-
Trash2, Pencil, Layers, ScrollText, FolderInput,
|
|
9
|
+
Trash2, Pencil, Layers, ScrollText, FolderInput, Copy,
|
|
10
10
|
} from 'lucide-react';
|
|
11
11
|
import { createFileAction, deleteFileAction, renameFileAction, renameSpaceAction, deleteSpaceAction, convertToSpaceAction, deleteFolderAction } from '@/lib/actions';
|
|
12
12
|
import { useLocale } from '@/lib/LocaleContext';
|
|
@@ -16,6 +16,10 @@ function notifyFilesChanged() {
|
|
|
16
16
|
window.dispatchEvent(new Event('mindos:files-changed'));
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
async function copyPathToClipboard(path: string) {
|
|
20
|
+
try { await navigator.clipboard.writeText(path); } catch { /* noop */ }
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
const SYSTEM_FILES = new Set(['INSTRUCTION.md', 'README.md']);
|
|
20
24
|
|
|
21
25
|
const HIDDEN_FILES_KEY = 'show-hidden-files';
|
|
@@ -141,6 +145,9 @@ function SpaceContextMenu({ x, y, node, onClose, onRename, onImport, onDelete }:
|
|
|
141
145
|
<FolderInput size={14} className="shrink-0" /> {t.fileTree.importFile}
|
|
142
146
|
</button>
|
|
143
147
|
)}
|
|
148
|
+
<button className={MENU_ITEM} onClick={() => { copyPathToClipboard(node.path); onClose(); }}>
|
|
149
|
+
<Copy size={14} className="shrink-0" /> {t.fileTree.copyPath}
|
|
150
|
+
</button>
|
|
144
151
|
<button className={MENU_ITEM} onClick={() => { onRename(); onClose(); }}>
|
|
145
152
|
<Pencil size={14} className="shrink-0" /> {t.fileTree.renameSpace}
|
|
146
153
|
</button>
|
|
@@ -173,6 +180,9 @@ function FolderContextMenu({ x, y, node, onClose, onRename, onDelete }: {
|
|
|
173
180
|
}}>
|
|
174
181
|
<Layers size={14} className="shrink-0 text-[var(--amber)]" /> {t.fileTree.convertToSpace}
|
|
175
182
|
</button>
|
|
183
|
+
<button className={MENU_ITEM} onClick={() => { copyPathToClipboard(node.path); onClose(); }}>
|
|
184
|
+
<Copy size={14} className="shrink-0" /> {t.fileTree.copyPath}
|
|
185
|
+
</button>
|
|
176
186
|
<button className={MENU_ITEM} onClick={() => { onRename(); onClose(); }}>
|
|
177
187
|
<Pencil size={14} className="shrink-0" /> {t.fileTree.rename}
|
|
178
188
|
</button>
|
|
@@ -643,6 +653,9 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
|
|
|
643
653
|
<span className="truncate leading-5" suppressHydrationWarning>{node.name}</span>
|
|
644
654
|
</button>
|
|
645
655
|
<div className="absolute right-1 top-1/2 -translate-y-1/2 hidden group-hover/file:flex items-center gap-0.5">
|
|
656
|
+
<button onClick={() => copyPathToClipboard(node.path)} className="p-0.5 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors" title={t.fileTree.copyPath}>
|
|
657
|
+
<Copy size={12} />
|
|
658
|
+
</button>
|
|
646
659
|
<button onClick={startRename} className="p-0.5 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors" title={t.fileTree.rename}>
|
|
647
660
|
<Pencil size={12} />
|
|
648
661
|
</button>
|
|
@@ -29,7 +29,9 @@ export default function GuideCard() {
|
|
|
29
29
|
setGuideState(null);
|
|
30
30
|
}
|
|
31
31
|
})
|
|
32
|
-
.catch(() => {
|
|
32
|
+
.catch((err) => {
|
|
33
|
+
console.warn('[GuideCard] Fetch guide state failed:', err);
|
|
34
|
+
});
|
|
33
35
|
}, []);
|
|
34
36
|
|
|
35
37
|
useEffect(() => {
|
|
@@ -54,7 +56,9 @@ export default function GuideCard() {
|
|
|
54
56
|
method: 'PATCH',
|
|
55
57
|
headers: { 'Content-Type': 'application/json' },
|
|
56
58
|
body: JSON.stringify({ guideState: patch }),
|
|
57
|
-
}).catch(() => {
|
|
59
|
+
}).catch((err) => {
|
|
60
|
+
console.warn('[GuideCard] PATCH guide state failed:', err);
|
|
61
|
+
});
|
|
58
62
|
}, []);
|
|
59
63
|
|
|
60
64
|
const handleDismiss = useCallback(() => {
|
|
@@ -159,7 +159,7 @@ function FeatureChip({ id, icon, name, entryPath, active, inactiveTitle }: {
|
|
|
159
159
|
);
|
|
160
160
|
|
|
161
161
|
if (active && entryPath) {
|
|
162
|
-
return <Link key={id} href={`/view/${encodePath(entryPath)}`}
|
|
162
|
+
return <Link key={id} href={`/view/${encodePath(entryPath)}`} className={cls}>{inner}</Link>;
|
|
163
163
|
}
|
|
164
164
|
return <span key={id} className={cls} title={inactiveTitle}>{inner}</span>;
|
|
165
165
|
}
|
|
@@ -618,7 +618,7 @@ function ExampleCleanupBanner() {
|
|
|
618
618
|
useEffect(() => {
|
|
619
619
|
scanExampleFilesAction().then(r => {
|
|
620
620
|
if (r.files.length > 0) setCount(r.files.length);
|
|
621
|
-
}).catch(() => {});
|
|
621
|
+
}).catch((err) => { console.warn("[HomeContent] scanExampleFilesAction failed:", err); });
|
|
622
622
|
}, []);
|
|
623
623
|
|
|
624
624
|
const handleCleanup = useCallback(async () => {
|
|
@@ -21,11 +21,14 @@ interface RightAskPanelProps {
|
|
|
21
21
|
onWidthCommit: (w: number) => void;
|
|
22
22
|
askMode?: 'panel' | 'popup';
|
|
23
23
|
onModeSwitch?: () => void;
|
|
24
|
+
maximized?: boolean;
|
|
25
|
+
onMaximize?: () => void;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export default function RightAskPanel({
|
|
27
29
|
open, onClose, currentFile, initialMessage, onFirstMessage,
|
|
28
30
|
width, onWidthChange, onWidthCommit, askMode, onModeSwitch,
|
|
31
|
+
maximized = false, onMaximize,
|
|
29
32
|
}: RightAskPanelProps) {
|
|
30
33
|
const handleMouseDown = useResizeDrag({
|
|
31
34
|
width,
|
|
@@ -42,10 +45,11 @@ export default function RightAskPanel({
|
|
|
42
45
|
className={`
|
|
43
46
|
hidden md:flex fixed top-0 right-0 h-screen z-40
|
|
44
47
|
flex-col bg-card border-l border-border
|
|
45
|
-
transition-
|
|
48
|
+
transition-all duration-200 ease-out
|
|
46
49
|
${open ? 'translate-x-0' : 'translate-x-full pointer-events-none'}
|
|
50
|
+
${maximized ? 'border-l-0' : ''}
|
|
47
51
|
`}
|
|
48
|
-
style={{ width: `${width}px` }}
|
|
52
|
+
style={{ width: maximized ? '100vw' : `${width}px` }}
|
|
49
53
|
role="complementary"
|
|
50
54
|
aria-label="MindOS Agent panel"
|
|
51
55
|
>
|
|
@@ -61,7 +65,6 @@ export default function RightAskPanel({
|
|
|
61
65
|
</button>
|
|
62
66
|
</div>
|
|
63
67
|
}>
|
|
64
|
-
{/* Flex column + min-h-0 so MessageList flex-1 gets a bounded height (fragment children are direct flex items) */}
|
|
65
68
|
<div className="flex min-h-0 w-full flex-1 flex-col overflow-hidden">
|
|
66
69
|
<AskContent
|
|
67
70
|
visible={open}
|
|
@@ -72,17 +75,21 @@ export default function RightAskPanel({
|
|
|
72
75
|
onClose={onClose}
|
|
73
76
|
askMode={askMode}
|
|
74
77
|
onModeSwitch={onModeSwitch}
|
|
78
|
+
maximized={maximized}
|
|
79
|
+
onMaximize={onMaximize}
|
|
75
80
|
/>
|
|
76
81
|
</div>
|
|
77
82
|
</ErrorBoundary>
|
|
78
83
|
|
|
79
|
-
{/* Drag resize handle — LEFT edge */}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
{/* Drag resize handle — LEFT edge (hidden when maximized) */}
|
|
85
|
+
{!maximized && (
|
|
86
|
+
<div
|
|
87
|
+
className="absolute top-0 -left-[3px] w-[6px] h-full cursor-col-resize z-40 group hidden md:block"
|
|
88
|
+
onMouseDown={handleMouseDown}
|
|
89
|
+
>
|
|
90
|
+
<div className="absolute left-[2px] top-0 w-[2px] h-full opacity-0 group-hover:opacity-100 bg-[var(--amber)]/60 transition-opacity" />
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
86
93
|
</aside>
|
|
87
94
|
);
|
|
88
95
|
}
|
|
@@ -171,13 +171,13 @@ export default function SearchModal({ open, onClose }: SearchModalProps) {
|
|
|
171
171
|
}
|
|
172
172
|
<div className="min-w-0 flex-1">
|
|
173
173
|
<div className="flex items-baseline gap-2 flex-wrap">
|
|
174
|
-
<span className="text-sm text-foreground font-medium truncate">{fileName}</span>
|
|
174
|
+
<span className="text-sm text-foreground font-medium truncate" title={fileName}>{fileName}</span>
|
|
175
175
|
{dirPath && (
|
|
176
|
-
<span className="text-xs text-muted-foreground truncate">{dirPath}</span>
|
|
176
|
+
<span className="text-xs text-muted-foreground truncate" title={dirPath}>{dirPath}</span>
|
|
177
177
|
)}
|
|
178
178
|
</div>
|
|
179
179
|
{result.snippet && (
|
|
180
|
-
<p className="text-xs text-muted-foreground mt-0.5 line-clamp-2 leading-relaxed">
|
|
180
|
+
<p className="text-xs text-muted-foreground mt-0.5 line-clamp-2 leading-relaxed" title={result.snippet}>
|
|
181
181
|
{highlightSnippet(result.snippet, query)}
|
|
182
182
|
</p>
|
|
183
183
|
)}
|
|
@@ -413,6 +413,8 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
413
413
|
onWidthCommit={ap.handleAskWidthCommit}
|
|
414
414
|
askMode={ap.askMode}
|
|
415
415
|
onModeSwitch={ap.handleAskModeSwitch}
|
|
416
|
+
maximized={ap.askMaximized}
|
|
417
|
+
onMaximize={ap.toggleAskMaximized}
|
|
416
418
|
/>
|
|
417
419
|
|
|
418
420
|
<RightAgentDetailPanel
|
|
@@ -493,7 +495,7 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
493
495
|
|
|
494
496
|
<main
|
|
495
497
|
id="main-content"
|
|
496
|
-
className=
|
|
498
|
+
className={`min-h-screen transition-all duration-200 pt-[52px] md:pt-0 ${ap.askMaximized ? 'hidden' : ''}`}
|
|
497
499
|
onDragEnter={(e) => {
|
|
498
500
|
if (!e.dataTransfer.types.includes('Files')) return;
|
|
499
501
|
e.preventDefault();
|
|
@@ -560,7 +562,7 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
560
562
|
<style>{`
|
|
561
563
|
@media (min-width: 768px) {
|
|
562
564
|
:root {
|
|
563
|
-
--right-panel-width: ${ap.askPanelOpen ? ap.askPanelWidth : 0}px;
|
|
565
|
+
--right-panel-width: ${ap.askMaximized ? '100vw' : `${ap.askPanelOpen ? ap.askPanelWidth : 0}px`};
|
|
564
566
|
--right-agent-detail-width: ${agentDockOpen ? agentDetailWidth : 0}px;
|
|
565
567
|
}
|
|
566
568
|
#main-content {
|
|
@@ -168,7 +168,7 @@ export default function SyncStatusBar({ collapsed, onOpenSyncSettings }: SyncSta
|
|
|
168
168
|
const prevLevelRef = useRef<StatusLevel>('off');
|
|
169
169
|
const [hintDismissed, setHintDismissed] = useState(() => {
|
|
170
170
|
if (typeof window !== 'undefined') {
|
|
171
|
-
try { return !!localStorage.getItem('sync-hint-dismissed'); } catch {}
|
|
171
|
+
try { return !!localStorage.getItem('sync-hint-dismissed'); } catch (err) { console.warn("[SyncStatusBar] localStorage read failed:", err); }
|
|
172
172
|
}
|
|
173
173
|
return false;
|
|
174
174
|
});
|
|
@@ -219,7 +219,7 @@ export default function SyncStatusBar({ collapsed, onOpenSyncSettings }: SyncSta
|
|
|
219
219
|
<button
|
|
220
220
|
onClick={(e) => {
|
|
221
221
|
e.stopPropagation();
|
|
222
|
-
try { localStorage.setItem('sync-hint-dismissed', '1'); } catch {}
|
|
222
|
+
try { localStorage.setItem('sync-hint-dismissed', '1'); } catch (err) { console.warn("[SyncStatusBar] localStorage write dismissed:", err); }
|
|
223
223
|
setHintDismissed(true);
|
|
224
224
|
}}
|
|
225
225
|
className="p-1 rounded hover:bg-muted hover:text-foreground transition-colors shrink-0 ml-2 text-muted-foreground/50 hover:text-muted-foreground"
|
|
@@ -442,7 +442,7 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
|
|
|
442
442
|
} else if (typeof errBody?.message === 'string' && errBody.message.trim()) {
|
|
443
443
|
errorMsg = errBody.message;
|
|
444
444
|
}
|
|
445
|
-
} catch {}
|
|
445
|
+
} catch (err) { console.warn("[AskContent] error body parse failed:", err); }
|
|
446
446
|
const err = new Error(errorMsg);
|
|
447
447
|
(err as Error & { httpStatus?: number }).httpStatus = res.status;
|
|
448
448
|
throw err;
|
|
@@ -601,24 +601,24 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
|
|
|
601
601
|
</span>
|
|
602
602
|
</div>
|
|
603
603
|
<div className="flex items-center gap-1">
|
|
604
|
-
<button type="button" onClick={() => setShowHistory(v => !v)} aria-pressed={showHistory} className={`p-1 rounded transition-colors ${showHistory ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted'}`} title="Session history">
|
|
604
|
+
<button type="button" onClick={() => setShowHistory(v => !v)} aria-pressed={showHistory} className={`p-1.5 rounded transition-colors ${showHistory ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted'}`} title="Session history">
|
|
605
605
|
<History size={iconSize} />
|
|
606
606
|
</button>
|
|
607
|
-
<button type="button" onClick={handleResetSession} disabled={isLoading} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors disabled:opacity-40" title="New session">
|
|
607
|
+
<button type="button" onClick={handleResetSession} disabled={isLoading} className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors disabled:opacity-40" title="New session">
|
|
608
608
|
<RotateCcw size={iconSize} />
|
|
609
609
|
</button>
|
|
610
610
|
{isPanel && onMaximize && (
|
|
611
|
-
<button type="button" onClick={onMaximize} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={maximized ? 'Restore panel' : 'Maximize panel'}>
|
|
611
|
+
<button type="button" onClick={onMaximize} className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={maximized ? 'Restore panel' : 'Maximize panel'}>
|
|
612
612
|
{maximized ? <Minimize2 size={iconSize} /> : <Maximize2 size={iconSize} />}
|
|
613
613
|
</button>
|
|
614
614
|
)}
|
|
615
615
|
{onModeSwitch && (
|
|
616
|
-
<button type="button" onClick={onModeSwitch} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={askMode === 'popup' ? 'Dock to side panel' : 'Open as popup'}>
|
|
616
|
+
<button type="button" onClick={onModeSwitch} className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={askMode === 'popup' ? 'Dock to side panel' : 'Open as popup'}>
|
|
617
617
|
{askMode === 'popup' ? <PanelRight size={iconSize} /> : <AppWindow size={iconSize} />}
|
|
618
618
|
</button>
|
|
619
619
|
)}
|
|
620
620
|
{onClose && (
|
|
621
|
-
<button type="button" onClick={onClose} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" aria-label="Close">
|
|
621
|
+
<button type="button" onClick={onClose} className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" aria-label="Close">
|
|
622
622
|
<X size={isPanel ? iconSize : 15} />
|
|
623
623
|
</button>
|
|
624
624
|
)}
|
|
@@ -22,7 +22,7 @@ export default function FileChip({ path, onRemove, variant = 'kb' }: FileChipPro
|
|
|
22
22
|
type="button"
|
|
23
23
|
onClick={onRemove}
|
|
24
24
|
aria-label={`Remove ${name}`}
|
|
25
|
-
className="
|
|
25
|
+
className="p-1 -mr-1 rounded hover:bg-muted hover:text-foreground transition-colors shrink-0"
|
|
26
26
|
>
|
|
27
27
|
<X size={10} />
|
|
28
28
|
</button>
|
|
@@ -54,9 +54,9 @@ export default function MentionPopover({ results, selectedIndex, query, onSelect
|
|
|
54
54
|
) : (
|
|
55
55
|
<FileText size={13} className="text-muted-foreground shrink-0" />
|
|
56
56
|
)}
|
|
57
|
-
<span className="truncate font-medium flex-1"><HighlightMatch text={name} query={query} /></span>
|
|
57
|
+
<span className="truncate font-medium flex-1" title={name}><HighlightMatch text={name} query={query} /></span>
|
|
58
58
|
{dir && (
|
|
59
|
-
<span className="text-2xs text-muted-foreground/40 truncate max-w-[140px] shrink-0">
|
|
59
|
+
<span className="text-2xs text-muted-foreground/40 truncate max-w-[140px] shrink-0" title={dir}>
|
|
60
60
|
<HighlightMatch text={dir} query={query} />
|
|
61
61
|
</span>
|
|
62
62
|
)}
|
|
@@ -137,7 +137,7 @@ export default function MessageList({
|
|
|
137
137
|
}, [messages]);
|
|
138
138
|
|
|
139
139
|
return (
|
|
140
|
-
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-4 min-h-0">
|
|
140
|
+
<div className="flex-1 overflow-y-auto overflow-x-hidden px-4 py-4 space-y-4 min-h-0">
|
|
141
141
|
{messages.length === 0 && (
|
|
142
142
|
<div className="mt-6 space-y-3">
|
|
143
143
|
<p className="text-center text-sm text-muted-foreground/60">{emptyPrompt}</p>
|
|
@@ -49,7 +49,7 @@ export default function SlashCommandPopover({ results, selectedIndex, query, onS
|
|
|
49
49
|
<Zap size={13} className="text-[var(--amber)] shrink-0" />
|
|
50
50
|
<span className="text-sm font-medium shrink-0">/<HighlightMatch text={item.name} query={query} /></span>
|
|
51
51
|
{item.description && (
|
|
52
|
-
<span className="text-2xs text-muted-foreground/50 truncate min-w-0 flex-1">{item.description}</span>
|
|
52
|
+
<span className="text-2xs text-muted-foreground/50 truncate min-w-0 flex-1" title={item.description}>{item.description}</span>
|
|
53
53
|
)}
|
|
54
54
|
</button>
|
|
55
55
|
))}
|
|
@@ -20,10 +20,10 @@ export default function UseCaseCard({ icon, title, description, prompt, tryItLab
|
|
|
20
20
|
{icon}
|
|
21
21
|
</span>
|
|
22
22
|
<div className="flex-1 min-w-0">
|
|
23
|
-
<h3 className="text-sm font-semibold font-display truncate text-foreground">
|
|
23
|
+
<h3 className="text-sm font-semibold font-display truncate text-foreground" title={title}>
|
|
24
24
|
{title}
|
|
25
25
|
</h3>
|
|
26
|
-
<p className="text-xs leading-relaxed mt-1 line-clamp-2 text-muted-foreground">
|
|
26
|
+
<p className="text-xs leading-relaxed mt-1 line-clamp-2 text-muted-foreground" title={description}>
|
|
27
27
|
{description}
|
|
28
28
|
</p>
|
|
29
29
|
</div>
|
|
@@ -59,7 +59,12 @@ function PromptBlock({ text, copyLabel }: { text: string; copyLabel: string }) {
|
|
|
59
59
|
navigator.clipboard.writeText(clean).then(() => {
|
|
60
60
|
setCopied(true);
|
|
61
61
|
setTimeout(() => setCopied(false), 1500);
|
|
62
|
-
}).catch(() => {
|
|
62
|
+
}).catch((err) => {
|
|
63
|
+
console.error('[HelpContent] Clipboard copy failed:', err);
|
|
64
|
+
// Show error feedback in UI
|
|
65
|
+
setCopied(true); // Reuse copied state to show error
|
|
66
|
+
setTimeout(() => setCopied(false), 2000);
|
|
67
|
+
});
|
|
63
68
|
}, [text]);
|
|
64
69
|
|
|
65
70
|
return (
|
|
@@ -85,7 +85,7 @@ export default function AgentsPanelAgentDetail({
|
|
|
85
85
|
<header className="shrink-0 flex items-center justify-between gap-3 border-b border-border px-4 py-3 bg-card">
|
|
86
86
|
<div className="flex items-center gap-2.5 min-w-0">
|
|
87
87
|
<span className={`w-2 h-2 rounded-full shrink-0 ${dot}`} />
|
|
88
|
-
<h2 className="text-sm font-semibold text-foreground truncate font-display">{agent.name}</h2>
|
|
88
|
+
<h2 className="text-sm font-semibold text-foreground truncate font-display" title={agent.name}>{agent.name}</h2>
|
|
89
89
|
</div>
|
|
90
90
|
<button
|
|
91
91
|
type="button"
|
|
@@ -107,7 +107,7 @@ export default function AgentsPanelAgentDetail({
|
|
|
107
107
|
{copy.backToList}
|
|
108
108
|
</button>
|
|
109
109
|
<span className={`w-1.5 h-1.5 rounded-full shrink-0 ${dot}`} />
|
|
110
|
-
<span className="text-sm font-medium text-foreground truncate">{agent.name}</span>
|
|
110
|
+
<span className="text-sm font-medium text-foreground truncate" title={agent.name}>{agent.name}</span>
|
|
111
111
|
</div>
|
|
112
112
|
)}
|
|
113
113
|
|
|
@@ -32,7 +32,7 @@ function UseCaseRow({
|
|
|
32
32
|
return (
|
|
33
33
|
<div className="group flex items-center gap-2.5 px-4 py-1.5 hover:bg-muted/50 transition-colors rounded-sm mx-1">
|
|
34
34
|
<span className="text-muted-foreground shrink-0">{icon}</span>
|
|
35
|
-
<span className="text-xs text-foreground truncate flex-1">{title}</span>
|
|
35
|
+
<span className="text-xs text-foreground truncate flex-1" title={title}>{title}</span>
|
|
36
36
|
<button
|
|
37
37
|
onClick={() => openAskModal(prompt, 'user')}
|
|
38
38
|
className="opacity-0 group-hover:opacity-100 text-2xs px-2 py-0.5 rounded text-[var(--amber-text)] bg-[var(--amber-dim)] hover:opacity-80 transition-all duration-150 shrink-0"
|
|
@@ -85,7 +85,7 @@ export default function DiscoverPanel({ active, maximized, onMaximize }: Discove
|
|
|
85
85
|
const pathSet = new Set(allPaths);
|
|
86
86
|
setExistingFiles(new Set(entryPaths.filter(ep => pathSet.has(ep))));
|
|
87
87
|
})
|
|
88
|
-
.catch(() => {});
|
|
88
|
+
.catch((err) => { console.warn("[DiscoverPanel] fetch /api/files failed:", err); });
|
|
89
89
|
}, [pluginsMounted]);
|
|
90
90
|
|
|
91
91
|
const handleToggle = useCallback((id: string, enabled: boolean) => {
|
|
@@ -170,7 +170,7 @@ export default function DiscoverPanel({ active, maximized, onMaximize }: Discove
|
|
|
170
170
|
onKeyDown={canOpen ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleOpenPlugin(r.entryPath!); } } : undefined}
|
|
171
171
|
>
|
|
172
172
|
<span className="text-sm shrink-0" suppressHydrationWarning>{r.icon}</span>
|
|
173
|
-
<span className="text-xs text-foreground truncate flex-1">{r.name}</span>
|
|
173
|
+
<span className="text-xs text-foreground truncate flex-1" title={r.name}>{r.name}</span>
|
|
174
174
|
{r.core ? (
|
|
175
175
|
<span className="text-2xs px-1.5 py-0.5 rounded bg-muted text-muted-foreground shrink-0">{p.core}</span>
|
|
176
176
|
) : (
|
|
@@ -28,9 +28,9 @@ export function PanelNavRow({
|
|
|
28
28
|
<>
|
|
29
29
|
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-muted">{icon}</span>
|
|
30
30
|
<span className="flex-1 min-w-0">
|
|
31
|
-
<span className="block text-left text-sm font-medium text-foreground truncate">{title}</span>
|
|
31
|
+
<span className="block text-left text-sm font-medium text-foreground truncate" title={title}>{title}</span>
|
|
32
32
|
{subtitle ? (
|
|
33
|
-
<span className="block text-left text-2xs text-muted-foreground truncate">{subtitle}</span>
|
|
33
|
+
<span className="block text-left text-2xs text-muted-foreground truncate" title={subtitle}>{subtitle}</span>
|
|
34
34
|
) : null}
|
|
35
35
|
</span>
|
|
36
36
|
{badge}
|
|
@@ -44,7 +44,7 @@ export default function PluginsPanel({ active, maximized, onMaximize }: PluginsP
|
|
|
44
44
|
const pathSet = new Set(allPaths);
|
|
45
45
|
setExistingFiles(new Set(entryPaths.filter(p => pathSet.has(p))));
|
|
46
46
|
})
|
|
47
|
-
.catch(() => {});
|
|
47
|
+
.catch((err) => { console.warn("[PluginsPanel] fetch /api/files failed:", err); });
|
|
48
48
|
}, [mounted]);
|
|
49
49
|
|
|
50
50
|
const renderers = mounted ? getPluginRenderers() : [];
|
|
@@ -150,13 +150,13 @@ export default function SearchPanel({ active, onNavigate, maximized, onMaximize
|
|
|
150
150
|
}
|
|
151
151
|
<div className="min-w-0 flex-1">
|
|
152
152
|
<div className="flex items-baseline gap-2 flex-wrap">
|
|
153
|
-
<span className="text-sm text-foreground font-medium truncate">{fileName}</span>
|
|
153
|
+
<span className="text-sm text-foreground font-medium truncate" title={fileName}>{fileName}</span>
|
|
154
154
|
{dirPath && (
|
|
155
|
-
<span className="text-xs text-muted-foreground truncate">{dirPath}</span>
|
|
155
|
+
<span className="text-xs text-muted-foreground truncate" title={dirPath}>{dirPath}</span>
|
|
156
156
|
)}
|
|
157
157
|
</div>
|
|
158
158
|
{result.snippet && (
|
|
159
|
-
<p className="text-xs text-muted-foreground mt-0.5 line-clamp-2 leading-relaxed">
|
|
159
|
+
<p className="text-xs text-muted-foreground mt-0.5 line-clamp-2 leading-relaxed" title={result.snippet}>
|
|
160
160
|
{highlightSnippet(result.snippet, query)}
|
|
161
161
|
</p>
|
|
162
162
|
)}
|
|
@@ -55,7 +55,7 @@ export function SummaryRenderer({ filePath }: RendererContext) {
|
|
|
55
55
|
useEffect(() => {
|
|
56
56
|
apiFetch<RecentFile[]>(`/api/recent-files?limit=${LIMIT}`)
|
|
57
57
|
.then((data) => setRecentFiles(data.filter(f => f.path.endsWith('.md'))))
|
|
58
|
-
.catch(() => {});
|
|
58
|
+
.catch((err) => { console.warn("[SummaryRenderer] fetch recent-files failed:", err); });
|
|
59
59
|
}, [filePath]);
|
|
60
60
|
|
|
61
61
|
async function generate() {
|
|
@@ -51,7 +51,7 @@ export function AiTab({ data, updateAi, updateAgent, t }: AiTabProps) {
|
|
|
51
51
|
// Sync reconnectRetries to localStorage so AskContent can read it without fetching settings
|
|
52
52
|
useEffect(() => {
|
|
53
53
|
const v = data.agent?.reconnectRetries ?? 3;
|
|
54
|
-
try { localStorage.setItem('mindos-reconnect-retries', String(v)); } catch {}
|
|
54
|
+
try { localStorage.setItem('mindos-reconnect-retries', String(v)); } catch (err) { console.warn("[AiTab] localStorage setItem reconnectRetries failed:", err); }
|
|
55
55
|
}, [data.agent?.reconnectRetries]);
|
|
56
56
|
|
|
57
57
|
const handleTestKey = useCallback(async (providerName: 'anthropic' | 'openai') => {
|
|
@@ -271,7 +271,7 @@ export function AiTab({ data, updateAi, updateAgent, t }: AiTabProps) {
|
|
|
271
271
|
onChange={e => {
|
|
272
272
|
const v = Number(e.target.value);
|
|
273
273
|
updateAgent({ reconnectRetries: v });
|
|
274
|
-
try { localStorage.setItem('mindos-reconnect-retries', String(v)); } catch {}
|
|
274
|
+
try { localStorage.setItem('mindos-reconnect-retries', String(v)); } catch (err) { console.warn("[AiTab] localStorage setItem reconnectRetries failed:", err); }
|
|
275
275
|
}}
|
|
276
276
|
>
|
|
277
277
|
<option value="0">Off</option>
|
|
@@ -434,13 +434,13 @@ function AskDisplayMode() {
|
|
|
434
434
|
try {
|
|
435
435
|
const stored = localStorage.getItem('ask-mode');
|
|
436
436
|
if (stored === 'popup') setMode('popup');
|
|
437
|
-
} catch {}
|
|
437
|
+
} catch (err) { console.warn("[AiTab] localStorage getItem ask-mode failed:", err); }
|
|
438
438
|
}, []);
|
|
439
439
|
|
|
440
440
|
const handleChange = (value: string) => {
|
|
441
441
|
const next = value as 'panel' | 'popup';
|
|
442
442
|
setMode(next);
|
|
443
|
-
try { localStorage.setItem('ask-mode', next); } catch {}
|
|
443
|
+
try { localStorage.setItem('ask-mode', next); } catch (err) { console.warn("[AiTab] localStorage setItem ask-mode failed:", err); }
|
|
444
444
|
// Notify SidebarLayout to pick up the change
|
|
445
445
|
window.dispatchEvent(new StorageEvent('storage', { key: 'ask-mode', newValue: next }));
|
|
446
446
|
};
|
|
@@ -26,7 +26,7 @@ export function KnowledgeTab({ data, setData, t }: KnowledgeTabProps) {
|
|
|
26
26
|
const [cleanupResult, setCleanupResult] = useState<number | null>(null);
|
|
27
27
|
|
|
28
28
|
useEffect(() => {
|
|
29
|
-
scanExampleFilesAction().then(r => setExampleCount(r.files.length)).catch(() => {});
|
|
29
|
+
scanExampleFilesAction().then(r => setExampleCount(r.files.length)).catch((err) => { console.warn("[KnowledgeTab] scanExampleFilesAction failed:", err); });
|
|
30
30
|
}, []);
|
|
31
31
|
|
|
32
32
|
// Guide state toggle
|
|
@@ -52,15 +52,33 @@ export function McpTab({ t }: McpTabProps) {
|
|
|
52
52
|
restarting={restarting}
|
|
53
53
|
onRestart={async () => {
|
|
54
54
|
setRestarting(true);
|
|
55
|
-
try {
|
|
55
|
+
try {
|
|
56
|
+
await apiFetch('/api/mcp/restart', { method: 'POST' });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error('[McpTab] Restart request failed:', err);
|
|
59
|
+
setRestarting(false);
|
|
60
|
+
return; // Exit early, don't start polling if restart request fails
|
|
61
|
+
}
|
|
56
62
|
const deadline = Date.now() + 60_000;
|
|
57
63
|
clearInterval(restartPollRef.current);
|
|
58
64
|
restartPollRef.current = setInterval(async () => {
|
|
59
|
-
if (Date.now() > deadline) {
|
|
65
|
+
if (Date.now() > deadline) {
|
|
66
|
+
clearInterval(restartPollRef.current);
|
|
67
|
+
setRestarting(false);
|
|
68
|
+
console.warn('[McpTab] MCP restart timed out after 60s');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
60
71
|
try {
|
|
61
72
|
const s = await apiFetch<McpStatus>('/api/mcp/status', { timeout: 3000 });
|
|
62
|
-
if (s.running) {
|
|
63
|
-
|
|
73
|
+
if (s.running) {
|
|
74
|
+
clearInterval(restartPollRef.current);
|
|
75
|
+
setRestarting(false);
|
|
76
|
+
mcp.refresh();
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.warn('[McpTab] Status poll attempt failed:', err);
|
|
80
|
+
// Continue polling on individual failures
|
|
81
|
+
}
|
|
64
82
|
}, 3000);
|
|
65
83
|
}}
|
|
66
84
|
onRefresh={mcp.refresh}
|
|
@@ -80,7 +80,7 @@ function DesktopUpdateTab() {
|
|
|
80
80
|
useEffect(() => {
|
|
81
81
|
bridge.getAppInfo?.().then((info) => {
|
|
82
82
|
if (info?.version) setAppVersion(info.version);
|
|
83
|
-
}).catch(() => {});
|
|
83
|
+
}).catch((err) => { console.warn("[UpdateTab] getAppInfo failed:", err); });
|
|
84
84
|
handleCheck();
|
|
85
85
|
const cleanups: Array<() => void> = [];
|
|
86
86
|
if (bridge.onUpdateProgress) {
|
|
@@ -230,9 +230,15 @@ export default function SetupWizard() {
|
|
|
230
230
|
}, []);
|
|
231
231
|
|
|
232
232
|
const copyToken = useCallback(() => {
|
|
233
|
-
copyToClipboard(state.authToken)
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
copyToClipboard(state.authToken)
|
|
234
|
+
.then(() => {
|
|
235
|
+
setTokenCopied(true);
|
|
236
|
+
setTimeout(() => setTokenCopied(false), 2000);
|
|
237
|
+
})
|
|
238
|
+
.catch((err) => {
|
|
239
|
+
console.error('[Setup] Token copy failed:', err);
|
|
240
|
+
// Show error toast instead of success
|
|
241
|
+
});
|
|
236
242
|
}, [state.authToken]);
|
|
237
243
|
|
|
238
244
|
const checkPort = useCallback(async (port: number, which: 'web' | 'mcp') => {
|
|
@@ -42,7 +42,7 @@ export default function WalkthroughProvider({ children }: WalkthroughProviderPro
|
|
|
42
42
|
walkthroughDismissed: dismissed,
|
|
43
43
|
},
|
|
44
44
|
}),
|
|
45
|
-
}).catch(() => {});
|
|
45
|
+
}).catch((err) => { console.warn("[WalkthroughProvider] localStorage setItem failed:", err); });
|
|
46
46
|
}, []);
|
|
47
47
|
|
|
48
48
|
// Check for auto-start via ?welcome=1 or guideState
|
|
@@ -81,7 +81,7 @@ export default function WalkthroughProvider({ children }: WalkthroughProviderPro
|
|
|
81
81
|
setCurrentStep(gs.walkthroughStep);
|
|
82
82
|
}
|
|
83
83
|
})
|
|
84
|
-
.catch(() => {});
|
|
84
|
+
.catch((err) => { console.warn("[WalkthroughProvider] guideState read failed:", err); });
|
|
85
85
|
}, [totalSteps]);
|
|
86
86
|
|
|
87
87
|
const start = useCallback(() => {
|
package/app/hooks/useAskPanel.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { useAskModal } from './useAskModal';
|
|
|
7
7
|
export interface AskPanelState {
|
|
8
8
|
askPanelOpen: boolean;
|
|
9
9
|
askPanelWidth: number;
|
|
10
|
+
askMaximized: boolean;
|
|
10
11
|
askMode: 'panel' | 'popup';
|
|
11
12
|
desktopAskPopupOpen: boolean;
|
|
12
13
|
askInitialMessage: string;
|
|
@@ -17,6 +18,7 @@ export interface AskPanelState {
|
|
|
17
18
|
handleAskWidthChange: (w: number) => void;
|
|
18
19
|
handleAskWidthCommit: (w: number) => void;
|
|
19
20
|
handleAskModeSwitch: () => void;
|
|
21
|
+
toggleAskMaximized: () => void;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -29,6 +31,7 @@ export function useAskPanel(): AskPanelState {
|
|
|
29
31
|
const [askMode, setAskMode] = useState<'panel' | 'popup'>('panel');
|
|
30
32
|
const [desktopAskPopupOpen, setDesktopAskPopupOpen] = useState(false);
|
|
31
33
|
const [askInitialMessage, setAskInitialMessage] = useState('');
|
|
34
|
+
const [askMaximized, setAskMaximized] = useState(false);
|
|
32
35
|
const [askOpenSource, setAskOpenSource] = useState<'user' | 'guide' | 'guide-next'>('user');
|
|
33
36
|
|
|
34
37
|
const askModal = useAskModal();
|
|
@@ -82,7 +85,8 @@ export function useAskPanel(): AskPanelState {
|
|
|
82
85
|
}
|
|
83
86
|
}, [askMode]);
|
|
84
87
|
|
|
85
|
-
const closeAskPanel = useCallback(() => setAskPanelOpen(false), []);
|
|
88
|
+
const closeAskPanel = useCallback(() => { setAskPanelOpen(false); setAskMaximized(false); }, []);
|
|
89
|
+
const toggleAskMaximized = useCallback(() => setAskMaximized(v => !v), []);
|
|
86
90
|
const closeDesktopAskPopup = useCallback(() => setDesktopAskPopupOpen(false), []);
|
|
87
91
|
|
|
88
92
|
const handleAskWidthChange = useCallback((w: number) => setAskPanelWidth(w), []);
|
|
@@ -109,9 +113,9 @@ export function useAskPanel(): AskPanelState {
|
|
|
109
113
|
}, []);
|
|
110
114
|
|
|
111
115
|
return {
|
|
112
|
-
askPanelOpen, askPanelWidth, askMode, desktopAskPopupOpen,
|
|
116
|
+
askPanelOpen, askPanelWidth, askMaximized, askMode, desktopAskPopupOpen,
|
|
113
117
|
askInitialMessage, askOpenSource,
|
|
114
118
|
toggleAskPanel, closeAskPanel, closeDesktopAskPopup,
|
|
115
|
-
handleAskWidthChange, handleAskWidthCommit, handleAskModeSwitch,
|
|
119
|
+
handleAskWidthChange, handleAskWidthCommit, handleAskModeSwitch, toggleAskMaximized,
|
|
116
120
|
};
|
|
117
121
|
}
|
|
@@ -106,7 +106,7 @@ export function useFileImport() {
|
|
|
106
106
|
const merged = [...prev];
|
|
107
107
|
for (const f of newFiles) {
|
|
108
108
|
const isDup = merged.some(m =>
|
|
109
|
-
m.name === f.name && m.size === f.size
|
|
109
|
+
m.name === f.name && m.size === f.size && m.file.lastModified === f.file.lastModified
|
|
110
110
|
);
|
|
111
111
|
if (!isDup && merged.length < MAX_FILES) merged.push(f);
|
|
112
112
|
}
|