@geminilight/mindos 0.6.23 → 0.6.27
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 +19 -3
- package/README_zh.md +19 -3
- package/app/app/.well-known/agent-card.json/route.ts +34 -0
- package/app/app/api/a2a/discover/route.ts +23 -0
- package/app/app/api/a2a/route.ts +100 -0
- package/app/components/Backlinks.tsx +2 -2
- package/app/components/Breadcrumb.tsx +1 -1
- package/app/components/CreateSpaceModal.tsx +1 -0
- package/app/components/CsvView.tsx +41 -19
- package/app/components/DirView.tsx +2 -2
- package/app/components/GuideCard.tsx +6 -2
- package/app/components/HomeContent.tsx +1 -1
- package/app/components/ImportModal.tsx +3 -0
- package/app/components/OnboardingView.tsx +1 -0
- package/app/components/RightAskPanel.tsx +4 -2
- package/app/components/SearchModal.tsx +3 -3
- package/app/components/SidebarLayout.tsx +11 -2
- package/app/components/SyncStatusBar.tsx +2 -2
- package/app/components/agents/DiscoverAgentModal.tsx +149 -0
- package/app/components/ask/AskContent.tsx +22 -10
- package/app/components/ask/MentionPopover.tsx +2 -2
- package/app/components/ask/SessionTabBar.tsx +70 -0
- package/app/components/ask/SlashCommandPopover.tsx +1 -1
- package/app/components/echo/EchoInsightCollapsible.tsx +4 -0
- package/app/components/explore/UseCaseCard.tsx +2 -2
- package/app/components/help/HelpContent.tsx +6 -1
- package/app/components/panels/AgentsPanel.tsx +25 -2
- 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/renderers/workflow/WorkflowRenderer.tsx +5 -0
- package/app/components/settings/AiTab.tsx +5 -4
- package/app/components/settings/KnowledgeTab.tsx +3 -1
- package/app/components/settings/McpTab.tsx +22 -4
- package/app/components/settings/SyncTab.tsx +2 -0
- package/app/components/settings/UpdateTab.tsx +1 -1
- package/app/components/setup/StepDots.tsx +5 -1
- package/app/components/setup/index.tsx +9 -3
- package/app/components/walkthrough/WalkthroughProvider.tsx +2 -2
- package/app/data/skills/mindos/SKILL.md +186 -0
- package/app/data/skills/mindos-zh/SKILL.md +185 -0
- package/app/hooks/useA2aRegistry.ts +53 -0
- package/app/hooks/useAskSession.ts +44 -25
- package/app/lib/a2a/a2a-tools.ts +212 -0
- package/app/lib/a2a/agent-card.ts +107 -0
- package/app/lib/a2a/client.ts +207 -0
- package/app/lib/a2a/index.ts +31 -0
- package/app/lib/a2a/orchestrator.ts +255 -0
- package/app/lib/a2a/task-handler.ts +228 -0
- package/app/lib/a2a/types.ts +212 -0
- package/app/lib/agent/tools.ts +6 -4
- package/app/lib/i18n-en.ts +52 -0
- package/app/lib/i18n-zh.ts +52 -0
- package/app/next-env.d.ts +1 -1
- package/bin/cli.js +183 -164
- package/bin/commands/agent.js +110 -0
- package/bin/commands/api.js +60 -0
- package/bin/commands/ask.js +3 -3
- package/bin/commands/file.js +13 -13
- package/bin/commands/search.js +51 -0
- package/bin/commands/space.js +64 -10
- package/bin/lib/command.js +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { X, Loader2, Globe, AlertCircle, CheckCircle2 } from 'lucide-react';
|
|
5
|
+
import { useLocale } from '@/lib/LocaleContext';
|
|
6
|
+
import type { RemoteAgent } from '@/lib/a2a/types';
|
|
7
|
+
|
|
8
|
+
interface DiscoverAgentModalProps {
|
|
9
|
+
open: boolean;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
onDiscover: (url: string) => Promise<RemoteAgent | null>;
|
|
12
|
+
discovering: boolean;
|
|
13
|
+
error: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function DiscoverAgentModal({
|
|
17
|
+
open,
|
|
18
|
+
onClose,
|
|
19
|
+
onDiscover,
|
|
20
|
+
discovering,
|
|
21
|
+
error,
|
|
22
|
+
}: DiscoverAgentModalProps) {
|
|
23
|
+
const { t } = useLocale();
|
|
24
|
+
const p = t.panels.agents;
|
|
25
|
+
const [url, setUrl] = useState('');
|
|
26
|
+
const [result, setResult] = useState<RemoteAgent | null>(null);
|
|
27
|
+
|
|
28
|
+
if (!open) return null;
|
|
29
|
+
|
|
30
|
+
const handleDiscover = async () => {
|
|
31
|
+
if (!url.trim() || discovering) return;
|
|
32
|
+
setResult(null);
|
|
33
|
+
const agent = await onDiscover(url.trim());
|
|
34
|
+
if (agent) setResult(agent);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
38
|
+
if (e.key === 'Enter') handleDiscover();
|
|
39
|
+
if (e.key === 'Escape') onClose();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const handleClose = () => {
|
|
43
|
+
setUrl('');
|
|
44
|
+
setResult(null);
|
|
45
|
+
onClose();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40" onClick={handleClose}>
|
|
50
|
+
<div
|
|
51
|
+
className="bg-popover border border-border rounded-xl shadow-lg w-full max-w-md mx-4 p-5"
|
|
52
|
+
onClick={e => e.stopPropagation()}
|
|
53
|
+
role="dialog"
|
|
54
|
+
aria-modal="true"
|
|
55
|
+
aria-label={p.a2aDiscover}
|
|
56
|
+
>
|
|
57
|
+
<div className="flex items-center justify-between mb-4">
|
|
58
|
+
<h3 className="text-sm font-semibold text-foreground flex items-center gap-2">
|
|
59
|
+
<Globe size={15} className="text-muted-foreground" />
|
|
60
|
+
{p.a2aDiscover}
|
|
61
|
+
</h3>
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
onClick={handleClose}
|
|
65
|
+
className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
66
|
+
aria-label="Close"
|
|
67
|
+
>
|
|
68
|
+
<X size={14} />
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<p className="text-2xs text-muted-foreground mb-3">{p.a2aDiscoverHint}</p>
|
|
73
|
+
|
|
74
|
+
<div className="flex gap-2 mb-4">
|
|
75
|
+
<input
|
|
76
|
+
type="url"
|
|
77
|
+
value={url}
|
|
78
|
+
onChange={e => setUrl(e.target.value)}
|
|
79
|
+
onKeyDown={handleKeyDown}
|
|
80
|
+
placeholder={p.a2aDiscoverPlaceholder}
|
|
81
|
+
disabled={discovering}
|
|
82
|
+
className="flex-1 px-3 py-2 text-xs rounded-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-50"
|
|
83
|
+
autoFocus
|
|
84
|
+
/>
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
onClick={handleDiscover}
|
|
88
|
+
disabled={discovering || !url.trim()}
|
|
89
|
+
className="px-3 py-2 text-xs font-medium rounded-lg bg-foreground text-background hover:bg-foreground/90 disabled:opacity-50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring flex items-center gap-1.5 shrink-0"
|
|
90
|
+
>
|
|
91
|
+
{discovering && <Loader2 size={12} className="animate-spin" />}
|
|
92
|
+
{discovering ? p.a2aDiscovering : p.a2aDiscover}
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
{error && !result && (
|
|
97
|
+
<div className="rounded-lg border border-error/30 bg-error/5 px-3 py-2.5 mb-3">
|
|
98
|
+
<div className="flex items-start gap-2">
|
|
99
|
+
<AlertCircle size={14} className="text-error mt-0.5 shrink-0" />
|
|
100
|
+
<div>
|
|
101
|
+
<p className="text-xs font-medium text-error">{p.a2aDiscoverFailed}</p>
|
|
102
|
+
<p className="text-2xs text-muted-foreground mt-0.5">{p.a2aDiscoverFailedHint}</p>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
{result && (
|
|
109
|
+
<div className="rounded-lg border border-success/30 bg-success/5 px-3 py-2.5">
|
|
110
|
+
<div className="flex items-start gap-2">
|
|
111
|
+
<CheckCircle2 size={14} className="text-success mt-0.5 shrink-0" />
|
|
112
|
+
<div className="min-w-0">
|
|
113
|
+
<p className="text-xs font-medium text-success mb-1.5">{p.a2aDiscoverSuccess}</p>
|
|
114
|
+
<div className="space-y-1">
|
|
115
|
+
<p className="text-xs font-medium text-foreground truncate" title={result.card.name}>
|
|
116
|
+
{result.card.name}
|
|
117
|
+
<span className="text-2xs text-muted-foreground ml-1.5">v{result.card.version}</span>
|
|
118
|
+
</p>
|
|
119
|
+
<p className="text-2xs text-muted-foreground truncate" title={result.card.description}>
|
|
120
|
+
{result.card.description}
|
|
121
|
+
</p>
|
|
122
|
+
{result.card.skills.length > 0 && (
|
|
123
|
+
<div className="mt-1.5">
|
|
124
|
+
<p className="text-2xs text-muted-foreground mb-1">{p.a2aSkills}:</p>
|
|
125
|
+
<div className="flex flex-wrap gap-1">
|
|
126
|
+
{result.card.skills.map(s => (
|
|
127
|
+
<span
|
|
128
|
+
key={s.id}
|
|
129
|
+
className="text-2xs px-1.5 py-0.5 rounded bg-muted/80 text-muted-foreground border border-border/50"
|
|
130
|
+
title={s.description}
|
|
131
|
+
>
|
|
132
|
+
{s.name}
|
|
133
|
+
</span>
|
|
134
|
+
))}
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
<p className="text-2xs text-muted-foreground mt-1 truncate" title={result.endpoint}>
|
|
139
|
+
{p.a2aEndpoint}: {result.endpoint}
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useEffect, useLayoutEffect, useRef, useState, useCallback } from 'react';
|
|
4
|
-
import { Sparkles, Send, Paperclip, StopCircle,
|
|
4
|
+
import { Sparkles, Send, Paperclip, StopCircle, SquarePen, History, X, Zap, Maximize2, Minimize2, PanelRight, AppWindow } from 'lucide-react';
|
|
5
5
|
import { useLocale } from '@/lib/LocaleContext';
|
|
6
6
|
import type { Message } from '@/lib/types';
|
|
7
7
|
import { useAskSession } from '@/hooks/useAskSession';
|
|
@@ -13,6 +13,7 @@ import MessageList from '@/components/ask/MessageList';
|
|
|
13
13
|
import MentionPopover from '@/components/ask/MentionPopover';
|
|
14
14
|
import SlashCommandPopover from '@/components/ask/SlashCommandPopover';
|
|
15
15
|
import SessionHistory from '@/components/ask/SessionHistory';
|
|
16
|
+
import SessionTabBar from '@/components/ask/SessionTabBar';
|
|
16
17
|
import FileChip from '@/components/ask/FileChip';
|
|
17
18
|
import { consumeUIMessageStream } from '@/lib/agent/stream-consumer';
|
|
18
19
|
import { isRetryableError, retryDelay, sleep } from '@/lib/agent/reconnect';
|
|
@@ -442,7 +443,7 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
|
|
|
442
443
|
} else if (typeof errBody?.message === 'string' && errBody.message.trim()) {
|
|
443
444
|
errorMsg = errBody.message;
|
|
444
445
|
}
|
|
445
|
-
} catch {}
|
|
446
|
+
} catch (err) { console.warn("[AskContent] error body parse failed:", err); }
|
|
446
447
|
const err = new Error(errorMsg);
|
|
447
448
|
(err as Error & { httpStatus?: number }).httpStatus = res.status;
|
|
448
449
|
throw err;
|
|
@@ -601,30 +602,41 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
|
|
|
601
602
|
</span>
|
|
602
603
|
</div>
|
|
603
604
|
<div className="flex items-center gap-1">
|
|
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=
|
|
605
|
+
<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={t.hints.sessionHistory}>
|
|
605
606
|
<History size={iconSize} />
|
|
606
607
|
</button>
|
|
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=
|
|
608
|
-
<
|
|
608
|
+
<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={t.hints.newSession}>
|
|
609
|
+
<SquarePen size={iconSize} />
|
|
609
610
|
</button>
|
|
610
611
|
{isPanel && onMaximize && (
|
|
611
|
-
<button type="button" onClick={onMaximize} className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={maximized ?
|
|
612
|
+
<button type="button" onClick={onMaximize} className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={maximized ? t.hints.restorePanel : t.hints.maximizePanel}>
|
|
612
613
|
{maximized ? <Minimize2 size={iconSize} /> : <Maximize2 size={iconSize} />}
|
|
613
614
|
</button>
|
|
614
615
|
)}
|
|
615
616
|
{onModeSwitch && (
|
|
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' ?
|
|
617
|
+
<button type="button" onClick={onModeSwitch} className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={askMode === 'popup' ? t.hints.dockToSide : t.hints.openAsPopup}>
|
|
617
618
|
{askMode === 'popup' ? <PanelRight size={iconSize} /> : <AppWindow size={iconSize} />}
|
|
618
619
|
</button>
|
|
619
620
|
)}
|
|
620
621
|
{onClose && (
|
|
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
|
+
<button type="button" onClick={onClose} className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={t.hints.closePanel} aria-label="Close">
|
|
622
623
|
<X size={isPanel ? iconSize : 15} />
|
|
623
624
|
</button>
|
|
624
625
|
)}
|
|
625
626
|
</div>
|
|
626
627
|
</div>
|
|
627
628
|
|
|
629
|
+
{/* Session tabs — panel variant only */}
|
|
630
|
+
{isPanel && session.sessions.length > 0 && (
|
|
631
|
+
<SessionTabBar
|
|
632
|
+
sessions={session.sessions}
|
|
633
|
+
activeSessionId={session.activeSessionId}
|
|
634
|
+
onLoad={handleLoadSession}
|
|
635
|
+
onDelete={session.deleteSession}
|
|
636
|
+
onNew={handleResetSession}
|
|
637
|
+
/>
|
|
638
|
+
)}
|
|
639
|
+
|
|
628
640
|
{showHistory && (
|
|
629
641
|
<SessionHistory
|
|
630
642
|
sessions={session.sessions}
|
|
@@ -770,7 +782,7 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
|
|
|
770
782
|
isPanel ? 'min-h-0 flex-1 items-end gap-1.5 px-2 py-2' : 'items-end gap-2 px-3 py-3',
|
|
771
783
|
)}
|
|
772
784
|
>
|
|
773
|
-
<button type="button" onClick={() => upload.uploadInputRef.current?.click()} className="p-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0" title=
|
|
785
|
+
<button type="button" onClick={() => upload.uploadInputRef.current?.click()} className="p-1.5 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors shrink-0" title={t.hints.attachFile}>
|
|
774
786
|
<Paperclip size={inputIconSize} />
|
|
775
787
|
</button>
|
|
776
788
|
|
|
@@ -807,7 +819,7 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
|
|
|
807
819
|
{loadingPhase === 'reconnecting' ? <X size={inputIconSize} /> : <StopCircle size={inputIconSize} />}
|
|
808
820
|
</button>
|
|
809
821
|
) : (
|
|
810
|
-
<button type="submit" disabled={!input.trim() || mention.mentionQuery !== null || slash.slashQuery !== null} className="p-1.5 rounded-md disabled:opacity-40 disabled:cursor-not-allowed transition-opacity shrink-0 bg-[var(--amber)] text-[var(--amber-foreground)]">
|
|
822
|
+
<button type="submit" disabled={!input.trim() || mention.mentionQuery !== null || slash.slashQuery !== null} title={!input.trim() ? t.hints.typeMessage : mention.mentionQuery !== null || slash.slashQuery !== null ? t.hints.mentionInProgress : undefined} className="p-1.5 rounded-md disabled:opacity-40 disabled:cursor-not-allowed transition-opacity shrink-0 bg-[var(--amber)] text-[var(--amber-foreground)]">
|
|
811
823
|
<Send size={isPanel ? 13 : 14} />
|
|
812
824
|
</button>
|
|
813
825
|
)}
|
|
@@ -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
|
)}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Plus, X } from 'lucide-react';
|
|
4
|
+
import type { ChatSession } from '@/lib/types';
|
|
5
|
+
import { sessionTitle } from '@/hooks/useAskSession';
|
|
6
|
+
import { useLocale } from '@/lib/LocaleContext';
|
|
7
|
+
|
|
8
|
+
interface SessionTabBarProps {
|
|
9
|
+
sessions: ChatSession[];
|
|
10
|
+
activeSessionId: string | null;
|
|
11
|
+
onLoad: (id: string) => void;
|
|
12
|
+
onDelete: (id: string) => void;
|
|
13
|
+
onNew: () => void;
|
|
14
|
+
maxTabs?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function SessionTabBar({
|
|
18
|
+
sessions, activeSessionId, onLoad, onDelete, onNew, maxTabs = 3,
|
|
19
|
+
}: SessionTabBarProps) {
|
|
20
|
+
const { t } = useLocale();
|
|
21
|
+
const visibleSessions = sessions.slice(0, maxTabs);
|
|
22
|
+
|
|
23
|
+
if (visibleSessions.length === 0) return null;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="flex items-center border-b border-border shrink-0 bg-background/50">
|
|
27
|
+
<div className="flex flex-1 min-w-0">
|
|
28
|
+
{visibleSessions.map((s) => {
|
|
29
|
+
const isActive = s.id === activeSessionId;
|
|
30
|
+
const title = sessionTitle(s);
|
|
31
|
+
return (
|
|
32
|
+
<button
|
|
33
|
+
key={s.id}
|
|
34
|
+
type="button"
|
|
35
|
+
onClick={() => onLoad(s.id)}
|
|
36
|
+
className={`group relative flex items-center gap-1 min-w-0 max-w-[160px] px-3 py-2 text-xs transition-colors
|
|
37
|
+
${isActive
|
|
38
|
+
? 'text-foreground border-b-2 border-[var(--amber)] bg-card'
|
|
39
|
+
: 'text-muted-foreground hover:text-foreground hover:bg-muted/50 border-b-2 border-transparent'
|
|
40
|
+
}`}
|
|
41
|
+
title={title}
|
|
42
|
+
>
|
|
43
|
+
<span className="truncate">{title === '(empty session)' ? t.hints.newChat : title}</span>
|
|
44
|
+
{visibleSessions.length > 1 && (
|
|
45
|
+
<span
|
|
46
|
+
role="button"
|
|
47
|
+
tabIndex={0}
|
|
48
|
+
onClick={(e) => { e.stopPropagation(); onDelete(s.id); }}
|
|
49
|
+
onKeyDown={(e) => { if (e.key === 'Enter') { e.stopPropagation(); onDelete(s.id); } }}
|
|
50
|
+
className="shrink-0 p-0.5 rounded opacity-0 group-hover:opacity-100 hover:bg-muted hover:text-error transition-opacity"
|
|
51
|
+
title={t.hints.closeSession}
|
|
52
|
+
>
|
|
53
|
+
<X size={10} />
|
|
54
|
+
</span>
|
|
55
|
+
)}
|
|
56
|
+
</button>
|
|
57
|
+
);
|
|
58
|
+
})}
|
|
59
|
+
</div>
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
onClick={onNew}
|
|
63
|
+
className="shrink-0 p-2 text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors"
|
|
64
|
+
title={t.hints.newChat}
|
|
65
|
+
>
|
|
66
|
+
<Plus size={13} />
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -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
|
))}
|
|
@@ -7,6 +7,7 @@ import remarkGfm from 'remark-gfm';
|
|
|
7
7
|
import { cn } from '@/lib/utils';
|
|
8
8
|
import { consumeUIMessageStream } from '@/lib/agent/stream-consumer';
|
|
9
9
|
import { useSettingsAiAvailable } from '@/hooks/useSettingsAiAvailable';
|
|
10
|
+
import { useLocale } from '@/lib/LocaleContext';
|
|
10
11
|
|
|
11
12
|
const proseInsight =
|
|
12
13
|
'prose prose-sm prose-panel dark:prose-invert max-w-none text-foreground ' +
|
|
@@ -50,6 +51,7 @@ export function EchoInsightCollapsible({
|
|
|
50
51
|
const btnId = `${panelId}-btn`;
|
|
51
52
|
const abortRef = useRef<AbortController | null>(null);
|
|
52
53
|
const { ready: aiReady, loading: aiLoading } = useSettingsAiAvailable();
|
|
54
|
+
const { t } = useLocale();
|
|
53
55
|
|
|
54
56
|
useEffect(() => () => abortRef.current?.abort(), []);
|
|
55
57
|
|
|
@@ -145,6 +147,7 @@ export function EchoInsightCollapsible({
|
|
|
145
147
|
<button
|
|
146
148
|
type="button"
|
|
147
149
|
disabled={generateDisabled}
|
|
150
|
+
title={generateDisabled ? t.hints.aiNotConfigured : undefined}
|
|
148
151
|
onClick={runGenerate}
|
|
149
152
|
className="inline-flex items-center gap-2 rounded-lg bg-[var(--amber)] px-3 py-2 font-sans text-sm font-medium text-[var(--amber-foreground)] transition-opacity duration-150 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
150
153
|
>
|
|
@@ -160,6 +163,7 @@ export function EchoInsightCollapsible({
|
|
|
160
163
|
type="button"
|
|
161
164
|
onClick={runGenerate}
|
|
162
165
|
disabled={streaming || !aiReady}
|
|
166
|
+
title={streaming || !aiReady ? t.hints.generationInProgress : undefined}
|
|
163
167
|
className="font-sans text-sm text-[var(--amber)] underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
|
|
164
168
|
>
|
|
165
169
|
{retryLabel}
|
|
@@ -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 (
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { usePathname } from 'next/navigation';
|
|
5
|
-
import { Loader2, RefreshCw, Settings } from 'lucide-react';
|
|
5
|
+
import { Globe, Loader2, RefreshCw, Settings } from 'lucide-react';
|
|
6
6
|
import { useMcpData } from '@/hooks/useMcpData';
|
|
7
|
+
import { useA2aRegistry } from '@/hooks/useA2aRegistry';
|
|
7
8
|
import { useLocale } from '@/lib/LocaleContext';
|
|
8
9
|
import PanelHeader from './PanelHeader';
|
|
9
10
|
import { AgentsPanelHubNav } from './AgentsPanelHubNav';
|
|
10
11
|
import { AgentsPanelAgentGroups } from './AgentsPanelAgentGroups';
|
|
12
|
+
import DiscoverAgentModal from '../agents/DiscoverAgentModal';
|
|
11
13
|
|
|
12
14
|
interface AgentsPanelProps {
|
|
13
15
|
active: boolean;
|
|
@@ -28,6 +30,8 @@ export default function AgentsPanel({
|
|
|
28
30
|
const pathname = usePathname();
|
|
29
31
|
const [refreshing, setRefreshing] = useState(false);
|
|
30
32
|
const [showNotDetected, setShowNotDetected] = useState(false);
|
|
33
|
+
const [showDiscoverModal, setShowDiscoverModal] = useState(false);
|
|
34
|
+
const a2a = useA2aRegistry();
|
|
31
35
|
|
|
32
36
|
const handleRefresh = async () => {
|
|
33
37
|
setRefreshing(true);
|
|
@@ -82,6 +86,9 @@ export default function AgentsPanel({
|
|
|
82
86
|
{!mcp.loading && (
|
|
83
87
|
<span className="text-2xs text-muted-foreground">
|
|
84
88
|
{connected.length} {p.connected}
|
|
89
|
+
{a2a.agents.length > 0 && (
|
|
90
|
+
<> · {p.a2aLabel} {a2a.agents.length}</>
|
|
91
|
+
)}
|
|
85
92
|
</span>
|
|
86
93
|
)}
|
|
87
94
|
<button
|
|
@@ -146,7 +153,15 @@ export default function AgentsPanel({
|
|
|
146
153
|
)}
|
|
147
154
|
</div>
|
|
148
155
|
|
|
149
|
-
<div className="px-3 py-2 border-t border-border shrink-0">
|
|
156
|
+
<div className="px-3 py-2 border-t border-border shrink-0 space-y-1">
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
onClick={() => setShowDiscoverModal(true)}
|
|
160
|
+
className="flex items-center gap-1.5 text-2xs text-muted-foreground hover:text-foreground transition-colors w-full focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
|
|
161
|
+
>
|
|
162
|
+
<Globe size={11} />
|
|
163
|
+
{p.a2aDiscover}
|
|
164
|
+
</button>
|
|
150
165
|
<button
|
|
151
166
|
type="button"
|
|
152
167
|
onClick={openAdvancedConfig}
|
|
@@ -156,6 +171,14 @@ export default function AgentsPanel({
|
|
|
156
171
|
{p.advancedConfig}
|
|
157
172
|
</button>
|
|
158
173
|
</div>
|
|
174
|
+
|
|
175
|
+
<DiscoverAgentModal
|
|
176
|
+
open={showDiscoverModal}
|
|
177
|
+
onClose={() => setShowDiscoverModal(false)}
|
|
178
|
+
onDiscover={a2a.discover}
|
|
179
|
+
discovering={a2a.discovering}
|
|
180
|
+
error={a2a.error}
|
|
181
|
+
/>
|
|
159
182
|
</div>
|
|
160
183
|
);
|
|
161
184
|
}
|
|
@@ -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() {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useMemo, useState, useRef, useCallback } from 'react';
|
|
4
4
|
import { Play, SkipForward, RotateCcw, CheckCircle2, Circle, Loader2, AlertCircle, ChevronDown, Sparkles } from 'lucide-react';
|
|
5
5
|
import type { RendererContext } from '@/lib/renderers/registry';
|
|
6
|
+
import { useLocale } from '@/lib/LocaleContext';
|
|
6
7
|
|
|
7
8
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
8
9
|
|
|
@@ -171,6 +172,7 @@ function StepCard({
|
|
|
171
172
|
onSkip: () => void;
|
|
172
173
|
canRun: boolean;
|
|
173
174
|
}) {
|
|
175
|
+
const { t } = useLocale();
|
|
174
176
|
const [expanded, setExpanded] = useState(false);
|
|
175
177
|
const hasBody = step.body.trim().length > 0;
|
|
176
178
|
const hasOutput = step.output.length > 0;
|
|
@@ -201,6 +203,7 @@ function StepCard({
|
|
|
201
203
|
<button
|
|
202
204
|
onClick={onRun}
|
|
203
205
|
disabled={!canRun}
|
|
206
|
+
title={!canRun ? t.hints.workflowStepRunning : undefined}
|
|
204
207
|
style={{
|
|
205
208
|
display: 'flex', alignItems: 'center', gap: 4,
|
|
206
209
|
padding: '3px 10px', borderRadius: 6, fontSize: '0.72rem',
|
|
@@ -268,6 +271,7 @@ function StepCard({
|
|
|
268
271
|
// ─── Main renderer ────────────────────────────────────────────────────────────
|
|
269
272
|
|
|
270
273
|
export function WorkflowRenderer({ filePath, content }: RendererContext) {
|
|
274
|
+
const { t } = useLocale();
|
|
271
275
|
const parsed = useMemo(() => parseWorkflow(content), [content]);
|
|
272
276
|
const [steps, setSteps] = useState<WorkflowStep[]>(() => parsed.steps);
|
|
273
277
|
const [running, setRunning] = useState(false);
|
|
@@ -357,6 +361,7 @@ export function WorkflowRenderer({ filePath, content }: RendererContext) {
|
|
|
357
361
|
<button
|
|
358
362
|
onClick={() => runStep(nextPendingIdx)}
|
|
359
363
|
disabled={running}
|
|
364
|
+
title={running ? t.hints.workflowRunning : undefined}
|
|
360
365
|
style={{
|
|
361
366
|
display: 'flex', alignItems: 'center', gap: 5,
|
|
362
367
|
padding: '4px 12px', borderRadius: 7, fontSize: '0.75rem',
|
|
@@ -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') => {
|
|
@@ -125,6 +125,7 @@ export function AiTab({ data, updateAi, updateAgent, t }: AiTabProps) {
|
|
|
125
125
|
<button
|
|
126
126
|
type="button"
|
|
127
127
|
disabled={disabled}
|
|
128
|
+
title={disabled ? t.hints.testInProgressOrNoKey : undefined}
|
|
128
129
|
onClick={() => handleTestKey(providerName)}
|
|
129
130
|
className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:border-foreground/20 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
|
130
131
|
>
|
|
@@ -271,7 +272,7 @@ export function AiTab({ data, updateAi, updateAgent, t }: AiTabProps) {
|
|
|
271
272
|
onChange={e => {
|
|
272
273
|
const v = Number(e.target.value);
|
|
273
274
|
updateAgent({ reconnectRetries: v });
|
|
274
|
-
try { localStorage.setItem('mindos-reconnect-retries', String(v)); } catch {}
|
|
275
|
+
try { localStorage.setItem('mindos-reconnect-retries', String(v)); } catch (err) { console.warn("[AiTab] localStorage setItem reconnectRetries failed:", err); }
|
|
275
276
|
}}
|
|
276
277
|
>
|
|
277
278
|
<option value="0">Off</option>
|
|
@@ -434,13 +435,13 @@ function AskDisplayMode() {
|
|
|
434
435
|
try {
|
|
435
436
|
const stored = localStorage.getItem('ask-mode');
|
|
436
437
|
if (stored === 'popup') setMode('popup');
|
|
437
|
-
} catch {}
|
|
438
|
+
} catch (err) { console.warn("[AiTab] localStorage getItem ask-mode failed:", err); }
|
|
438
439
|
}, []);
|
|
439
440
|
|
|
440
441
|
const handleChange = (value: string) => {
|
|
441
442
|
const next = value as 'panel' | 'popup';
|
|
442
443
|
setMode(next);
|
|
443
|
-
try { localStorage.setItem('ask-mode', next); } catch {}
|
|
444
|
+
try { localStorage.setItem('ask-mode', next); } catch (err) { console.warn("[AiTab] localStorage setItem ask-mode failed:", err); }
|
|
444
445
|
// Notify SidebarLayout to pick up the change
|
|
445
446
|
window.dispatchEvent(new StorageEvent('storage', { key: 'ask-mode', newValue: next }));
|
|
446
447
|
};
|