@geminilight/mindos 0.5.52 → 0.5.55
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 +7 -7
- package/README_zh.md +5 -5
- package/app/app/echo/[segment]/page.tsx +15 -0
- package/app/app/echo/page.tsx +6 -0
- package/app/components/ActivityBar.tsx +3 -2
- package/app/components/Panel.tsx +1 -0
- package/app/components/RightAgentDetailPanel.tsx +121 -0
- package/app/components/RightAskPanel.tsx +14 -11
- package/app/components/SidebarLayout.tsx +69 -5
- package/app/components/ask/AskContent.tsx +10 -2
- package/app/components/echo/EchoHero.tsx +55 -0
- package/app/components/echo/EchoInsightCollapsible.tsx +184 -0
- package/app/components/echo/EchoPageSections.tsx +86 -0
- package/app/components/echo/EchoSegmentNav.tsx +58 -0
- package/app/components/echo/EchoSegmentPageClient.tsx +265 -0
- package/app/components/panels/AgentsPanel.tsx +156 -178
- package/app/components/panels/AgentsPanelAgentDetail.tsx +193 -0
- package/app/components/panels/AgentsPanelAgentGroups.tsx +116 -0
- package/app/components/panels/AgentsPanelAgentListRow.tsx +101 -0
- package/app/components/panels/AgentsPanelHubNav.tsx +48 -0
- package/app/components/panels/DiscoverPanel.tsx +6 -46
- package/app/components/panels/EchoPanel.tsx +49 -0
- package/app/components/panels/PanelNavRow.tsx +68 -0
- package/app/components/panels/agents-panel-resolve-status.ts +13 -0
- package/app/hooks/useSettingsAiAvailable.ts +29 -0
- package/app/lib/echo-insight-prompt.ts +44 -0
- package/app/lib/echo-segments.ts +27 -0
- package/app/lib/i18n-en.ts +62 -2
- package/app/lib/i18n-zh.ts +59 -2
- package/app/lib/settings-ai-client.ts +26 -0
- package/app/next-env.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ MindOS is where you think, and where your AI agents act — a local-first knowle
|
|
|
40
40
|
>
|
|
41
41
|
> **✨ Try it now:** After installation, give these a try:
|
|
42
42
|
> ```
|
|
43
|
-
>
|
|
43
|
+
> Here's my resume, read it and organize my info into MindOS.
|
|
44
44
|
> ```
|
|
45
45
|
> ```
|
|
46
46
|
> Help me distill the experience from this conversation into MindOS as a reusable SOP.
|
|
@@ -51,19 +51,19 @@ MindOS is where you think, and where your AI agents act — a local-first knowle
|
|
|
51
51
|
|
|
52
52
|
## 🧠 Human-AI Shared Mind
|
|
53
53
|
|
|
54
|
-
>
|
|
54
|
+
> You shape AI through thinking, AI empowers you through execution. Human and AI, growing together in one shared brain.
|
|
55
55
|
|
|
56
56
|
**1. Global Sync — Breaking Memory Silos**
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Switch tools or start a new chat and you're re-transporting context, scattering knowledge. **With a built-in MCP server (20+ tools), MindOS connects all Agents to your core knowledge base with zero config. Record profile and project memory once to empower all AI tools.**
|
|
59
59
|
|
|
60
|
-
**2. Transparent & Controllable — No
|
|
60
|
+
**2. Transparent & Controllable — No Black Boxes**
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
Agent memory locked in black boxes makes reasoning unauditable, erasing trust as hallucinations compound. **MindOS saves every retrieval, reflection & action as local plain text. You hold absolute mind-correction rights with a full GUI to recalibrate Agents anytime.**
|
|
63
63
|
|
|
64
|
-
**3. Symbiotic Evolution — Experience Flows Back
|
|
64
|
+
**3. Symbiotic Evolution — Experience Flows Back As Instructions**
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
You express preferences but the next chat starts from zero, leaving your thinking useless for AI. **MindOS auto-distills every thought into your knowledge base. Clarify your standards through interaction and sharpen your cognition with each iteration—AI will never repeat the same mistake.**
|
|
67
67
|
|
|
68
68
|
> **Foundation:** Local-first by default — all data stays in local plain text for privacy, ownership, and speed.
|
|
69
69
|
|
package/README_zh.md
CHANGED
|
@@ -40,7 +40,7 @@ MindOS 是你思考的地方,也是 AI Agent 行动的起点——一个你和
|
|
|
40
40
|
>
|
|
41
41
|
> **✨ 立即体验:** 安装完成后,不妨试试:
|
|
42
42
|
> ```
|
|
43
|
-
>
|
|
43
|
+
> 这是我的简历,读一下,把我的信息整理到 MindOS 里。
|
|
44
44
|
> ```
|
|
45
45
|
> ```
|
|
46
46
|
> 帮我把这次对话的经验沉淀到 MindOS,形成一个可复用的工作流。
|
|
@@ -51,19 +51,19 @@ MindOS 是你思考的地方,也是 AI Agent 行动的起点——一个你和
|
|
|
51
51
|
|
|
52
52
|
## 🧠 人机共享心智
|
|
53
53
|
|
|
54
|
-
>
|
|
54
|
+
> 你在思考中塑造 AI,AI 在执行中反哺你。人和 AI,在同一个大脑里共同成长。
|
|
55
55
|
|
|
56
56
|
**1. 全局同步 — 打破记忆割裂**
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
切换工具带来上下文割裂,个人深度背景散落各处,导致知识无法复用。**MindOS 内置 MCP Server (支持 20+ 工具),所有 Agent 零配置直连核心知识库。项目记忆与 SOP 仅需一处记录,全局赋能所有 AI 工具。**
|
|
59
59
|
|
|
60
60
|
**2. 透明可控 — 消除记忆黑箱**
|
|
61
61
|
|
|
62
|
-
Agent
|
|
62
|
+
Agent 记忆锁在黑箱中,推理无法审查,错误极难纠正且幻觉持续累积。**MindOS 将每次检索与执行沉淀为本地纯文本,提供完整的审查干预界面,人类拥有绝对的心智纠偏权,随时校准 Agent 行为。**
|
|
63
63
|
|
|
64
64
|
**3. 共生演进 — 经验回流为指令**
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
反复表达偏好但新对话又从零开始,思考未变成 AI 的能力与自身的方法论。**MindOS 将每次思考自动沉淀为知识库。在交互中厘清想法与标准,下次应对更默契拒绝重复犯错,认知随每次沉淀变得锐利。**
|
|
67
67
|
|
|
68
68
|
> **底层原则:** 默认本地优先,全部数据以本地纯文本保存,兼顾隐私、主权与性能。
|
|
69
69
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { notFound } from 'next/navigation';
|
|
2
|
+
import { isEchoSegment } from '@/lib/echo-segments';
|
|
3
|
+
import EchoSegmentPageClient from '@/components/echo/EchoSegmentPageClient';
|
|
4
|
+
|
|
5
|
+
interface PageProps {
|
|
6
|
+
params: Promise<{ segment: string }>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default async function EchoSegmentPage({ params }: PageProps) {
|
|
10
|
+
const { segment } = await params;
|
|
11
|
+
if (!isEchoSegment(segment)) {
|
|
12
|
+
notFound();
|
|
13
|
+
}
|
|
14
|
+
return <EchoSegmentPageClient segment={segment} />;
|
|
15
|
+
}
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { useRef, useCallback, useState, useEffect } from 'react';
|
|
4
4
|
import Link from 'next/link';
|
|
5
|
-
import { FolderTree, Search, Settings, RefreshCw, Blocks, Bot, Compass, HelpCircle, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
5
|
+
import { FolderTree, Search, Settings, RefreshCw, Blocks, Bot, Compass, HelpCircle, ChevronLeft, ChevronRight, Radio } from 'lucide-react';
|
|
6
6
|
import { useLocale } from '@/lib/LocaleContext';
|
|
7
7
|
import { DOT_COLORS, getStatusLevel } from './SyncStatusBar';
|
|
8
8
|
import type { SyncStatus } from './settings/SyncTab';
|
|
9
9
|
import Logo from './Logo';
|
|
10
10
|
|
|
11
|
-
export type PanelId = 'files' | 'search' | 'plugins' | 'agents' | 'discover';
|
|
11
|
+
export type PanelId = 'files' | 'search' | 'echo' | 'plugins' | 'agents' | 'discover';
|
|
12
12
|
|
|
13
13
|
export const RAIL_WIDTH_COLLAPSED = 48;
|
|
14
14
|
export const RAIL_WIDTH_EXPANDED = 180;
|
|
@@ -157,6 +157,7 @@ export default function ActivityBar({
|
|
|
157
157
|
{/* ── Middle: Core panel toggles ── */}
|
|
158
158
|
<div className={`flex flex-col ${expanded ? 'px-1.5' : 'items-center'} gap-1 py-2`}>
|
|
159
159
|
<RailButton icon={<FolderTree size={18} />} label={t.sidebar.files} active={activePanel === 'files'} expanded={expanded} onClick={() => toggle('files')} walkthroughId="files-panel" />
|
|
160
|
+
<RailButton icon={<Radio size={18} />} label={t.sidebar.echo} active={activePanel === 'echo'} expanded={expanded} onClick={() => toggle('echo')} />
|
|
160
161
|
<RailButton icon={<Search size={18} />} label={t.sidebar.searchTitle} shortcut="⌘K" active={activePanel === 'search'} expanded={expanded} onClick={() => toggle('search')} walkthroughId="search-button" />
|
|
161
162
|
<RailButton icon={<Blocks size={18} />} label={t.sidebar.plugins} active={activePanel === 'plugins'} expanded={expanded} onClick={() => toggle('plugins')} />
|
|
162
163
|
<RailButton icon={<Bot size={18} />} label={t.sidebar.agents} active={activePanel === 'agents'} expanded={expanded} onClick={() => toggle('agents')} />
|
package/app/components/Panel.tsx
CHANGED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useMemo, useEffect } from 'react';
|
|
4
|
+
import { useMcpData } from '@/hooks/useMcpData';
|
|
5
|
+
import { useLocale } from '@/lib/LocaleContext';
|
|
6
|
+
import { useResizeDrag } from '@/hooks/useResizeDrag';
|
|
7
|
+
import AgentsPanelAgentDetail from '@/components/panels/AgentsPanelAgentDetail';
|
|
8
|
+
import { resolveAgentDetailStatus } from '@/components/panels/agents-panel-resolve-status';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_WIDTH = 400;
|
|
11
|
+
const MIN_WIDTH = 300;
|
|
12
|
+
const MAX_WIDTH_ABS = 640;
|
|
13
|
+
const MAX_WIDTH_RATIO = 0.42;
|
|
14
|
+
|
|
15
|
+
interface RightAgentDetailPanelProps {
|
|
16
|
+
open: boolean;
|
|
17
|
+
agentKey: string | null;
|
|
18
|
+
onClose: () => void;
|
|
19
|
+
/** Right offset in px when Ask panel is open (stack panels side-by-side). */
|
|
20
|
+
rightOffset: number;
|
|
21
|
+
width: number;
|
|
22
|
+
onWidthChange: (w: number) => void;
|
|
23
|
+
onWidthCommit: (w: number) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default function RightAgentDetailPanel({
|
|
27
|
+
open,
|
|
28
|
+
agentKey,
|
|
29
|
+
onClose,
|
|
30
|
+
rightOffset,
|
|
31
|
+
width,
|
|
32
|
+
onWidthChange,
|
|
33
|
+
onWidthCommit,
|
|
34
|
+
}: RightAgentDetailPanelProps) {
|
|
35
|
+
const mcp = useMcpData();
|
|
36
|
+
const { t } = useLocale();
|
|
37
|
+
const p = t.panels.agents;
|
|
38
|
+
|
|
39
|
+
const connected = mcp.agents.filter(a => a.present && a.installed);
|
|
40
|
+
const detected = mcp.agents.filter(a => a.present && !a.installed);
|
|
41
|
+
const notFound = mcp.agents.filter(a => !a.present);
|
|
42
|
+
|
|
43
|
+
const resolved = useMemo(() => {
|
|
44
|
+
if (!agentKey) return null;
|
|
45
|
+
const agent = mcp.agents.find(a => a.key === agentKey);
|
|
46
|
+
if (!agent) return null;
|
|
47
|
+
const status = resolveAgentDetailStatus(agentKey, connected, detected, notFound);
|
|
48
|
+
if (!status) return null;
|
|
49
|
+
return { agent, status };
|
|
50
|
+
}, [agentKey, mcp.agents, connected, detected, notFound]);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (agentKey && !resolved) {
|
|
54
|
+
const id = requestAnimationFrame(() => onClose());
|
|
55
|
+
return () => cancelAnimationFrame(id);
|
|
56
|
+
}
|
|
57
|
+
}, [agentKey, resolved, onClose]);
|
|
58
|
+
|
|
59
|
+
const handleMouseDown = useResizeDrag({
|
|
60
|
+
width,
|
|
61
|
+
minWidth: MIN_WIDTH,
|
|
62
|
+
maxWidth: MAX_WIDTH_ABS,
|
|
63
|
+
maxWidthRatio: MAX_WIDTH_RATIO,
|
|
64
|
+
direction: 'left',
|
|
65
|
+
onResize: onWidthChange,
|
|
66
|
+
onResizeEnd: onWidthCommit,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const detailCopy = {
|
|
70
|
+
connected: p.connected,
|
|
71
|
+
installing: p.installing,
|
|
72
|
+
install: p.install,
|
|
73
|
+
copyConfig: p.copyConfig,
|
|
74
|
+
copied: p.copied,
|
|
75
|
+
transportLocal: p.transportLocal,
|
|
76
|
+
transportRemote: p.transportRemote,
|
|
77
|
+
configPath: p.configPath,
|
|
78
|
+
notFoundDetail: p.notFoundDetail,
|
|
79
|
+
backToList: p.backToList,
|
|
80
|
+
closeDetail: p.closeAgentDetail,
|
|
81
|
+
agentDetailTransport: p.agentDetailTransport,
|
|
82
|
+
agentDetailSnippet: p.agentDetailSnippet,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<aside
|
|
87
|
+
className={`
|
|
88
|
+
hidden md:flex fixed top-0 h-screen z-[31] flex-col bg-card border-l border-border shadow-sm
|
|
89
|
+
transition-transform duration-200 ease-out
|
|
90
|
+
${open ? 'translate-x-0' : 'translate-x-full pointer-events-none'}
|
|
91
|
+
`}
|
|
92
|
+
style={{ width: `${width}px`, right: `${rightOffset}px` }}
|
|
93
|
+
role="complementary"
|
|
94
|
+
aria-label={p.agentDetailPanelAria}
|
|
95
|
+
aria-hidden={!open || !resolved}
|
|
96
|
+
>
|
|
97
|
+
{resolved && (
|
|
98
|
+
<div className="flex flex-col flex-1 min-h-0 overflow-hidden">
|
|
99
|
+
<AgentsPanelAgentDetail
|
|
100
|
+
agent={resolved.agent}
|
|
101
|
+
agentStatus={resolved.status}
|
|
102
|
+
mcpStatus={mcp.status}
|
|
103
|
+
onBack={onClose}
|
|
104
|
+
onInstallAgent={mcp.installAgent}
|
|
105
|
+
copy={detailCopy}
|
|
106
|
+
headerVariant="dock"
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
<div
|
|
112
|
+
className="absolute top-0 -left-[3px] w-[6px] h-full cursor-col-resize z-40 group hidden md:block"
|
|
113
|
+
onMouseDown={handleMouseDown}
|
|
114
|
+
>
|
|
115
|
+
<div className="absolute left-[2px] top-0 w-[2px] h-full opacity-0 group-hover:opacity-100 bg-[var(--amber)]/50 transition-opacity" />
|
|
116
|
+
</div>
|
|
117
|
+
</aside>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { DEFAULT_WIDTH as RIGHT_AGENT_DETAIL_DEFAULT_WIDTH, MIN_WIDTH as RIGHT_AGENT_DETAIL_MIN_WIDTH, MAX_WIDTH_ABS as RIGHT_AGENT_DETAIL_MAX_WIDTH };
|
|
@@ -40,7 +40,7 @@ export default function RightAskPanel({
|
|
|
40
40
|
return (
|
|
41
41
|
<aside
|
|
42
42
|
className={`
|
|
43
|
-
hidden md:flex fixed top-0 right-0 h-screen z-
|
|
43
|
+
hidden md:flex fixed top-0 right-0 h-screen z-40
|
|
44
44
|
flex-col bg-card border-l border-border
|
|
45
45
|
transition-transform duration-200 ease-out
|
|
46
46
|
${open ? 'translate-x-0' : 'translate-x-full pointer-events-none'}
|
|
@@ -61,16 +61,19 @@ export default function RightAskPanel({
|
|
|
61
61
|
</button>
|
|
62
62
|
</div>
|
|
63
63
|
}>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
{/* Flex column + min-h-0 so MessageList flex-1 gets a bounded height (fragment children are direct flex items) */}
|
|
65
|
+
<div className="flex min-h-0 w-full flex-1 flex-col overflow-hidden">
|
|
66
|
+
<AskContent
|
|
67
|
+
visible={open}
|
|
68
|
+
variant="panel"
|
|
69
|
+
currentFile={open ? currentFile : undefined}
|
|
70
|
+
initialMessage={initialMessage}
|
|
71
|
+
onFirstMessage={onFirstMessage}
|
|
72
|
+
onClose={onClose}
|
|
73
|
+
askMode={askMode}
|
|
74
|
+
onModeSwitch={onModeSwitch}
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
74
77
|
</ErrorBoundary>
|
|
75
78
|
|
|
76
79
|
{/* Drag resize handle — LEFT edge */}
|
|
@@ -12,7 +12,13 @@ import SearchPanel from './panels/SearchPanel';
|
|
|
12
12
|
import PluginsPanel from './panels/PluginsPanel';
|
|
13
13
|
import AgentsPanel from './panels/AgentsPanel';
|
|
14
14
|
import DiscoverPanel from './panels/DiscoverPanel';
|
|
15
|
+
import EchoPanel from './panels/EchoPanel';
|
|
15
16
|
import RightAskPanel from './RightAskPanel';
|
|
17
|
+
import RightAgentDetailPanel, {
|
|
18
|
+
RIGHT_AGENT_DETAIL_DEFAULT_WIDTH,
|
|
19
|
+
RIGHT_AGENT_DETAIL_MIN_WIDTH,
|
|
20
|
+
RIGHT_AGENT_DETAIL_MAX_WIDTH,
|
|
21
|
+
} from './RightAgentDetailPanel';
|
|
16
22
|
import AskFab from './AskFab';
|
|
17
23
|
import SyncPopover from './panels/SyncPopover';
|
|
18
24
|
import SearchModal from './SearchModal';
|
|
@@ -48,6 +54,20 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
48
54
|
const [syncPopoverOpen, setSyncPopoverOpen] = useState(false);
|
|
49
55
|
const [syncAnchorRect, setSyncAnchorRect] = useState<DOMRect | null>(null);
|
|
50
56
|
|
|
57
|
+
// ── Agent MCP detail (right dock, does not replace left Agents list) ──
|
|
58
|
+
const [agentDetailKey, setAgentDetailKey] = useState<string | null>(null);
|
|
59
|
+
const [agentDetailWidth, setAgentDetailWidth] = useState(() => {
|
|
60
|
+
if (typeof window === 'undefined') return RIGHT_AGENT_DETAIL_DEFAULT_WIDTH;
|
|
61
|
+
try {
|
|
62
|
+
const stored = localStorage.getItem('right-agent-detail-panel-width');
|
|
63
|
+
if (stored) {
|
|
64
|
+
const w = parseInt(stored, 10);
|
|
65
|
+
if (w >= RIGHT_AGENT_DETAIL_MIN_WIDTH && w <= RIGHT_AGENT_DETAIL_MAX_WIDTH) return w;
|
|
66
|
+
}
|
|
67
|
+
} catch { /* ignore */ }
|
|
68
|
+
return RIGHT_AGENT_DETAIL_DEFAULT_WIDTH;
|
|
69
|
+
});
|
|
70
|
+
|
|
51
71
|
// ── Mobile state ──
|
|
52
72
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
53
73
|
const [mobileSearchOpen, setMobileSearchOpen] = useState(false);
|
|
@@ -90,7 +110,28 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
90
110
|
}, [ap.askOpenSource]);
|
|
91
111
|
|
|
92
112
|
// Close mobile drawer on route change
|
|
93
|
-
useEffect(() => {
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
const id = requestAnimationFrame(() => setMobileOpen(false));
|
|
115
|
+
return () => cancelAnimationFrame(id);
|
|
116
|
+
}, [pathname]);
|
|
117
|
+
|
|
118
|
+
// Deep-link Echo routes: keep left Echo panel aligned with URL
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (pathname?.startsWith('/echo')) {
|
|
121
|
+
lp.setActivePanel('echo');
|
|
122
|
+
}
|
|
123
|
+
}, [pathname, lp.setActivePanel]);
|
|
124
|
+
|
|
125
|
+
const handleAgentDetailWidthCommit = useCallback((w: number) => {
|
|
126
|
+
setAgentDetailWidth(w);
|
|
127
|
+
try {
|
|
128
|
+
localStorage.setItem('right-agent-detail-panel-width', String(w));
|
|
129
|
+
} catch { /* ignore */ }
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
const closeAgentDetailPanel = useCallback(() => setAgentDetailKey(null), []);
|
|
133
|
+
|
|
134
|
+
const agentDockOpen = agentDetailKey !== null && lp.activePanel === 'agents';
|
|
94
135
|
|
|
95
136
|
// Refresh file tree periodically
|
|
96
137
|
useEffect(() => {
|
|
@@ -112,6 +153,7 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
112
153
|
const handler = (e: KeyboardEvent) => {
|
|
113
154
|
if (e.key === 'Escape') {
|
|
114
155
|
if (lp.panelMaximized) { lp.handlePanelMaximize(); return; }
|
|
156
|
+
if (agentDockOpen) { setAgentDetailKey(null); return; }
|
|
115
157
|
if (ap.askPanelOpen) { ap.closeAskPanel(); return; }
|
|
116
158
|
if (ap.desktopAskPopupOpen) { ap.closeDesktopAskPopup(); return; }
|
|
117
159
|
}
|
|
@@ -138,7 +180,7 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
138
180
|
};
|
|
139
181
|
window.addEventListener('keydown', handler);
|
|
140
182
|
return () => window.removeEventListener('keydown', handler);
|
|
141
|
-
}, [
|
|
183
|
+
}, [agentDockOpen, lp, ap]);
|
|
142
184
|
|
|
143
185
|
// ── Settings helpers ──
|
|
144
186
|
const openSyncSettings = useCallback(() => {
|
|
@@ -208,6 +250,9 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
208
250
|
maximized={lp.panelMaximized}
|
|
209
251
|
onMaximize={lp.handlePanelMaximize}
|
|
210
252
|
>
|
|
253
|
+
<div className={`flex flex-col h-full ${lp.activePanel === 'echo' ? '' : 'hidden'}`}>
|
|
254
|
+
<EchoPanel active={lp.activePanel === 'echo'} maximized={lp.panelMaximized} onMaximize={lp.handlePanelMaximize} />
|
|
255
|
+
</div>
|
|
211
256
|
<div className={`flex flex-col h-full ${lp.activePanel === 'search' ? '' : 'hidden'}`}>
|
|
212
257
|
<SearchPanel active={lp.activePanel === 'search'} maximized={lp.panelMaximized} onMaximize={lp.handlePanelMaximize} />
|
|
213
258
|
</div>
|
|
@@ -215,7 +260,13 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
215
260
|
<PluginsPanel active={lp.activePanel === 'plugins'} maximized={lp.panelMaximized} onMaximize={lp.handlePanelMaximize} />
|
|
216
261
|
</div>
|
|
217
262
|
<div className={`flex flex-col h-full ${lp.activePanel === 'agents' ? '' : 'hidden'}`}>
|
|
218
|
-
<AgentsPanel
|
|
263
|
+
<AgentsPanel
|
|
264
|
+
active={lp.activePanel === 'agents'}
|
|
265
|
+
maximized={lp.panelMaximized}
|
|
266
|
+
onMaximize={lp.handlePanelMaximize}
|
|
267
|
+
selectedAgentKey={agentDockOpen ? agentDetailKey : null}
|
|
268
|
+
onOpenAgentDetail={setAgentDetailKey}
|
|
269
|
+
/>
|
|
219
270
|
</div>
|
|
220
271
|
<div className={`flex flex-col h-full ${lp.activePanel === 'discover' ? '' : 'hidden'}`}>
|
|
221
272
|
<DiscoverPanel active={lp.activePanel === 'discover'} maximized={lp.panelMaximized} onMaximize={lp.handlePanelMaximize} />
|
|
@@ -236,6 +287,16 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
236
287
|
onModeSwitch={ap.handleAskModeSwitch}
|
|
237
288
|
/>
|
|
238
289
|
|
|
290
|
+
<RightAgentDetailPanel
|
|
291
|
+
open={agentDockOpen}
|
|
292
|
+
agentKey={agentDetailKey}
|
|
293
|
+
onClose={closeAgentDetailPanel}
|
|
294
|
+
rightOffset={ap.askPanelOpen ? ap.askPanelWidth : 0}
|
|
295
|
+
width={agentDetailWidth}
|
|
296
|
+
onWidthChange={setAgentDetailWidth}
|
|
297
|
+
onWidthCommit={handleAgentDetailWidthCommit}
|
|
298
|
+
/>
|
|
299
|
+
|
|
239
300
|
<AskModal
|
|
240
301
|
open={ap.desktopAskPopupOpen}
|
|
241
302
|
onClose={ap.closeDesktopAskPopup}
|
|
@@ -308,10 +369,13 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
308
369
|
|
|
309
370
|
<style>{`
|
|
310
371
|
@media (min-width: 768px) {
|
|
311
|
-
:root {
|
|
372
|
+
:root {
|
|
373
|
+
--right-panel-width: ${ap.askPanelOpen ? ap.askPanelWidth : 0}px;
|
|
374
|
+
--right-agent-detail-width: ${agentDockOpen ? agentDetailWidth : 0}px;
|
|
375
|
+
}
|
|
312
376
|
#main-content {
|
|
313
377
|
padding-left: ${lp.panelOpen && lp.panelMaximized ? '100vw' : `${lp.panelOpen ? lp.railWidth + lp.effectivePanelWidth : lp.railWidth}px`} !important;
|
|
314
|
-
padding-right: var(--right-panel-width) !important;
|
|
378
|
+
padding-right: calc(var(--right-panel-width) + var(--right-agent-detail-width)) !important;
|
|
315
379
|
padding-top: 0 !important;
|
|
316
380
|
}
|
|
317
381
|
}
|
|
@@ -12,6 +12,7 @@ import MentionPopover from '@/components/ask/MentionPopover';
|
|
|
12
12
|
import SessionHistory from '@/components/ask/SessionHistory';
|
|
13
13
|
import FileChip from '@/components/ask/FileChip';
|
|
14
14
|
import { consumeUIMessageStream } from '@/lib/agent/stream-consumer';
|
|
15
|
+
import { cn } from '@/lib/utils';
|
|
15
16
|
|
|
16
17
|
interface AskContentProps {
|
|
17
18
|
/** Controls visibility — 'open' for modal, 'active' for panel */
|
|
@@ -414,8 +415,15 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
|
|
|
414
415
|
</form>
|
|
415
416
|
</div>
|
|
416
417
|
|
|
417
|
-
{/* Footer hints */}
|
|
418
|
-
<div
|
|
418
|
+
{/* Footer hints — use full class strings so Tailwind JIT includes utilities */}
|
|
419
|
+
<div
|
|
420
|
+
className={cn(
|
|
421
|
+
'flex shrink-0 items-center',
|
|
422
|
+
isPanel
|
|
423
|
+
? 'gap-2 px-3 pb-1.5 text-[10px] text-muted-foreground/40'
|
|
424
|
+
: 'hidden gap-3 px-4 pb-2 text-xs text-muted-foreground/50 md:flex',
|
|
425
|
+
)}
|
|
426
|
+
>
|
|
419
427
|
<span><kbd className="font-mono">↵</kbd> {t.ask.send}</span>
|
|
420
428
|
<span><kbd className="font-mono">@</kbd> {t.ask.attachFile}</span>
|
|
421
429
|
{!isPanel && <span><kbd className="font-mono">ESC</kbd> {t.search.close}</span>}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Echo page hero: kicker, minimal breadcrumb (parent only — h1 holds the section title),
|
|
7
|
+
* lead. Avoids repeating the current segment name in both breadcrumb and h1.
|
|
8
|
+
*/
|
|
9
|
+
export function EchoHero({
|
|
10
|
+
breadcrumbNav,
|
|
11
|
+
parentHref,
|
|
12
|
+
parent,
|
|
13
|
+
heroKicker,
|
|
14
|
+
pageTitle,
|
|
15
|
+
lead,
|
|
16
|
+
titleId,
|
|
17
|
+
}: {
|
|
18
|
+
breadcrumbNav: string;
|
|
19
|
+
parentHref: string;
|
|
20
|
+
parent: string;
|
|
21
|
+
heroKicker: string;
|
|
22
|
+
pageTitle: string;
|
|
23
|
+
lead: string;
|
|
24
|
+
titleId: string;
|
|
25
|
+
}) {
|
|
26
|
+
return (
|
|
27
|
+
<header className="relative overflow-hidden rounded-xl border border-border bg-card px-5 py-6 shadow-sm sm:px-8 sm:py-8">
|
|
28
|
+
<div
|
|
29
|
+
className="absolute bottom-5 left-0 top-5 w-0.5 rounded-full bg-[var(--amber)] sm:bottom-6 sm:top-6"
|
|
30
|
+
aria-hidden
|
|
31
|
+
/>
|
|
32
|
+
<div className="relative pl-4 sm:pl-5">
|
|
33
|
+
<p className="mb-3 font-sans text-2xs font-semibold uppercase tracking-[0.2em] text-[var(--amber)]">
|
|
34
|
+
{heroKicker}
|
|
35
|
+
</p>
|
|
36
|
+
<nav aria-label={breadcrumbNav} className="mb-5 font-sans text-sm">
|
|
37
|
+
<ol className="m-0 list-none p-0">
|
|
38
|
+
<li>
|
|
39
|
+
<Link
|
|
40
|
+
href={parentHref}
|
|
41
|
+
className="text-muted-foreground transition-colors duration-150 hover:text-[var(--amber)] focus-visible:rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
42
|
+
>
|
|
43
|
+
{parent}
|
|
44
|
+
</Link>
|
|
45
|
+
</li>
|
|
46
|
+
</ol>
|
|
47
|
+
</nav>
|
|
48
|
+
<h1 id={titleId} className="font-display text-2xl font-semibold tracking-tight text-foreground md:text-3xl">
|
|
49
|
+
{pageTitle}
|
|
50
|
+
</h1>
|
|
51
|
+
<p className="mt-3 max-w-prose font-sans text-base leading-relaxed text-muted-foreground">{lead}</p>
|
|
52
|
+
</div>
|
|
53
|
+
</header>
|
|
54
|
+
);
|
|
55
|
+
}
|