@geminilight/mindos 0.6.16 → 0.6.18
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/app/app/globals.css +5 -2
- package/app/components/ActivityBar.tsx +8 -5
- package/app/components/CustomSelect.tsx +64 -36
- package/app/components/DirPicker.tsx +111 -81
- package/app/components/ImportModal.tsx +18 -11
- package/app/components/JsonView.tsx +2 -5
- package/app/components/Panel.tsx +0 -1
- package/app/components/SidebarLayout.tsx +10 -0
- package/app/components/UpdateOverlay.tsx +6 -6
- package/app/components/agents/AgentDetailContent.tsx +2 -2
- package/app/components/agents/AgentsMcpSection.tsx +2 -2
- package/app/components/agents/AgentsOverviewSection.tsx +11 -11
- package/app/components/agents/AgentsPrimitives.tsx +14 -14
- package/app/components/agents/AgentsSkillsSection.tsx +1 -1
- package/app/components/agents/SkillDetailPopover.tsx +1 -1
- package/app/components/ask/MessageList.tsx +1 -1
- package/app/components/panels/EchoPanel.tsx +7 -7
- package/app/components/renderers/summary/SummaryRenderer.tsx +1 -1
- package/package.json +1 -1
package/app/app/globals.css
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
--color-input: var(--input);
|
|
27
27
|
--color-border: var(--border);
|
|
28
28
|
--color-destructive: var(--destructive);
|
|
29
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
29
30
|
--color-success: var(--success);
|
|
30
31
|
--color-error: var(--error);
|
|
31
32
|
--color-amber-foreground: var(--amber-foreground);
|
|
@@ -69,7 +70,8 @@ body {
|
|
|
69
70
|
--muted-foreground: #685f52;
|
|
70
71
|
--accent: #d9d3c6;
|
|
71
72
|
--accent-foreground: #1c1a17;
|
|
72
|
-
--destructive: oklch(0.
|
|
73
|
+
--destructive: oklch(0.56 0.14 24);
|
|
74
|
+
--destructive-foreground: #ffffff;
|
|
73
75
|
--border: rgba(28, 26, 23, 0.1);
|
|
74
76
|
--input: rgba(28, 26, 23, 0.12);
|
|
75
77
|
--ring: var(--amber);
|
|
@@ -106,7 +108,8 @@ body {
|
|
|
106
108
|
--muted-foreground: #8a8275;
|
|
107
109
|
--accent: #2e2b22;
|
|
108
110
|
--accent-foreground: #e8e4dc;
|
|
109
|
-
--destructive: oklch(0.
|
|
111
|
+
--destructive: oklch(0.56 0.14 22);
|
|
112
|
+
--destructive-foreground: #ffffff;
|
|
110
113
|
--border: rgba(232, 228, 220, 0.08);
|
|
111
114
|
--input: rgba(232, 228, 220, 0.1);
|
|
112
115
|
--ring: var(--amber);
|
|
@@ -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, Bot, Compass, HelpCircle, ChevronLeft, ChevronRight, Radio
|
|
5
|
+
import { FolderTree, Search, Settings, RefreshCw, 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' | 'echo' | 'agents' | 'discover'
|
|
11
|
+
export type PanelId = 'files' | 'search' | 'echo' | 'agents' | 'discover';
|
|
12
12
|
|
|
13
13
|
export const RAIL_WIDTH_COLLAPSED = 48;
|
|
14
14
|
export const RAIL_WIDTH_EXPANDED = 180;
|
|
@@ -16,7 +16,9 @@ export const RAIL_WIDTH_EXPANDED = 180;
|
|
|
16
16
|
interface ActivityBarProps {
|
|
17
17
|
activePanel: PanelId | null;
|
|
18
18
|
onPanelChange: (id: PanelId | null) => void;
|
|
19
|
+
onEchoClick?: () => void;
|
|
19
20
|
onAgentsClick?: () => void;
|
|
21
|
+
onDiscoverClick?: () => void;
|
|
20
22
|
syncStatus: SyncStatus | null;
|
|
21
23
|
expanded: boolean;
|
|
22
24
|
onExpandedChange: (expanded: boolean) => void;
|
|
@@ -77,7 +79,9 @@ function RailButton({ icon, label, shortcut, active = false, expanded, onClick,
|
|
|
77
79
|
export default function ActivityBar({
|
|
78
80
|
activePanel,
|
|
79
81
|
onPanelChange,
|
|
82
|
+
onEchoClick,
|
|
80
83
|
onAgentsClick,
|
|
84
|
+
onDiscoverClick,
|
|
81
85
|
syncStatus,
|
|
82
86
|
expanded,
|
|
83
87
|
onExpandedChange,
|
|
@@ -188,7 +192,7 @@ export default function ActivityBar({
|
|
|
188
192
|
<div className={`flex flex-col ${expanded ? 'px-1.5' : 'items-center'} gap-1 py-2`}>
|
|
189
193
|
<RailButton icon={<FolderTree size={18} />} label={t.sidebar.files} active={activePanel === 'files'} expanded={expanded} onClick={() => toggle('files')} walkthroughId="files-panel" />
|
|
190
194
|
<RailButton icon={<Search size={18} />} label={t.sidebar.searchTitle} shortcut="⌘K" active={activePanel === 'search'} expanded={expanded} onClick={() => toggle('search')} />
|
|
191
|
-
<RailButton icon={<Radio size={18} />} label={t.sidebar.echo} active={activePanel === 'echo'} expanded={expanded} onClick={() => toggle('echo')} walkthroughId="echo-panel" />
|
|
195
|
+
<RailButton icon={<Radio size={18} />} label={t.sidebar.echo} active={activePanel === 'echo'} expanded={expanded} onClick={() => onEchoClick ? debounced(onEchoClick) : toggle('echo')} walkthroughId="echo-panel" />
|
|
192
196
|
<RailButton
|
|
193
197
|
icon={<Bot size={18} />}
|
|
194
198
|
label={t.sidebar.agents}
|
|
@@ -197,8 +201,7 @@ export default function ActivityBar({
|
|
|
197
201
|
onClick={() => onAgentsClick ? debounced(onAgentsClick) : toggle('agents')}
|
|
198
202
|
walkthroughId="agents-panel"
|
|
199
203
|
/>
|
|
200
|
-
<RailButton icon={<Compass size={18} />} label={t.sidebar.discover} active={activePanel === 'discover'} expanded={expanded} onClick={() => toggle('discover')} />
|
|
201
|
-
<RailButton icon={<History size={18} />} label={t.sidebar.history} active={activePanel === 'history'} expanded={expanded} onClick={() => toggle('history')} />
|
|
204
|
+
<RailButton icon={<Compass size={18} />} label={t.sidebar.discover} active={activePanel === 'discover'} expanded={expanded} onClick={() => onDiscoverClick ? debounced(onDiscoverClick) : toggle('discover')} />
|
|
202
205
|
</div>
|
|
203
206
|
|
|
204
207
|
{/* ── Spacer ── */}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
4
5
|
import { ChevronDown, Check } from 'lucide-react';
|
|
5
6
|
|
|
6
7
|
export interface SelectOption {
|
|
@@ -50,7 +51,7 @@ export default function CustomSelect({
|
|
|
50
51
|
}: CustomSelectProps) {
|
|
51
52
|
const [open, setOpen] = useState(false);
|
|
52
53
|
const [highlightIdx, setHighlightIdx] = useState(-1);
|
|
53
|
-
const [
|
|
54
|
+
const [panelPos, setPanelPos] = useState<{ top: number; left: number; width: number; flipUp: boolean } | null>(null);
|
|
54
55
|
const btnRef = useRef<HTMLButtonElement>(null);
|
|
55
56
|
const listRef = useRef<HTMLDivElement>(null);
|
|
56
57
|
|
|
@@ -122,20 +123,33 @@ export default function CustomSelect({
|
|
|
122
123
|
if (el) el.scrollIntoView({ block: 'nearest' });
|
|
123
124
|
}, [open, highlightIdx]);
|
|
124
125
|
|
|
125
|
-
|
|
126
|
+
const calcPosition = useCallback(() => {
|
|
127
|
+
if (!btnRef.current) return;
|
|
128
|
+
const rect = btnRef.current.getBoundingClientRect();
|
|
129
|
+
const maxH = size === 'sm' ? 200 : 260;
|
|
130
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
131
|
+
const spaceAbove = rect.top;
|
|
132
|
+
setPanelPos({
|
|
133
|
+
top: spaceBelow < maxH + 8 && spaceAbove > spaceBelow ? rect.top : rect.bottom,
|
|
134
|
+
left: rect.left,
|
|
135
|
+
width: rect.width,
|
|
136
|
+
flipUp: spaceBelow < maxH + 8 && spaceAbove > spaceBelow,
|
|
137
|
+
});
|
|
138
|
+
}, [size]);
|
|
139
|
+
|
|
140
|
+
// Initialize highlight + position when opening; reposition on scroll/resize
|
|
126
141
|
useEffect(() => {
|
|
127
|
-
if (open) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}, [open, allOptions, value, size]);
|
|
142
|
+
if (!open) { setPanelPos(null); return; }
|
|
143
|
+
const idx = allOptions.findIndex(o => o.value === value);
|
|
144
|
+
setHighlightIdx(idx >= 0 ? idx : 0);
|
|
145
|
+
calcPosition();
|
|
146
|
+
window.addEventListener('scroll', calcPosition, true);
|
|
147
|
+
window.addEventListener('resize', calcPosition);
|
|
148
|
+
return () => {
|
|
149
|
+
window.removeEventListener('scroll', calcPosition, true);
|
|
150
|
+
window.removeEventListener('resize', calcPosition);
|
|
151
|
+
};
|
|
152
|
+
}, [open, allOptions, value, calcPosition]);
|
|
139
153
|
|
|
140
154
|
const isSm = size === 'sm';
|
|
141
155
|
|
|
@@ -147,9 +161,9 @@ export default function CustomSelect({
|
|
|
147
161
|
? 'absolute right-1 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none'
|
|
148
162
|
: 'absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none';
|
|
149
163
|
|
|
150
|
-
const
|
|
151
|
-
?
|
|
152
|
-
:
|
|
164
|
+
const listBaseCls = isSm
|
|
165
|
+
? 'fixed z-[9999] overflow-y-auto rounded-md border border-border bg-card shadow-lg py-0.5'
|
|
166
|
+
: 'fixed z-[9999] overflow-y-auto rounded-lg border border-border bg-card shadow-lg py-1';
|
|
153
167
|
|
|
154
168
|
const itemBaseCls = isSm
|
|
155
169
|
? 'w-full flex items-center gap-1.5 px-2 py-1 text-2xs text-left transition-colors cursor-pointer'
|
|
@@ -184,6 +198,38 @@ export default function CustomSelect({
|
|
|
184
198
|
);
|
|
185
199
|
}
|
|
186
200
|
|
|
201
|
+
const listPortal = open && panelPos && createPortal(
|
|
202
|
+
<div
|
|
203
|
+
ref={listRef}
|
|
204
|
+
className={listBaseCls}
|
|
205
|
+
role="listbox"
|
|
206
|
+
style={{
|
|
207
|
+
left: panelPos.left,
|
|
208
|
+
minWidth: panelPos.width,
|
|
209
|
+
maxHeight: isSm ? 200 : 260,
|
|
210
|
+
...(panelPos.flipUp
|
|
211
|
+
? { bottom: window.innerHeight - panelPos.top + 4 }
|
|
212
|
+
: { top: panelPos.top + 4 }),
|
|
213
|
+
}}
|
|
214
|
+
>
|
|
215
|
+
{options.map((item, idx) => {
|
|
216
|
+
if (isGroup(item)) {
|
|
217
|
+
return (
|
|
218
|
+
<div key={item.label}>
|
|
219
|
+
{idx > 0 && <div className="my-0.5 border-t border-border/50" />}
|
|
220
|
+
<div className={`py-1 text-2xs font-medium text-muted-foreground uppercase tracking-wider ${isSm ? 'px-2' : 'px-3'}`}>
|
|
221
|
+
{item.label}
|
|
222
|
+
</div>
|
|
223
|
+
{item.options.map(renderOption)}
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return renderOption(item);
|
|
228
|
+
})}
|
|
229
|
+
</div>,
|
|
230
|
+
document.body,
|
|
231
|
+
);
|
|
232
|
+
|
|
187
233
|
return (
|
|
188
234
|
<div className="relative">
|
|
189
235
|
<button
|
|
@@ -204,25 +250,7 @@ export default function CustomSelect({
|
|
|
204
250
|
className={`${chevronCls} transition-transform duration-150 ${open ? 'rotate-180' : ''}`}
|
|
205
251
|
/>
|
|
206
252
|
</button>
|
|
207
|
-
|
|
208
|
-
{open && (
|
|
209
|
-
<div ref={listRef} className={listCls} role="listbox">
|
|
210
|
-
{options.map((item, idx) => {
|
|
211
|
-
if (isGroup(item)) {
|
|
212
|
-
return (
|
|
213
|
-
<div key={item.label}>
|
|
214
|
-
{idx > 0 && <div className="my-0.5 border-t border-border/50" />}
|
|
215
|
-
<div className={`py-1 text-2xs font-medium text-muted-foreground uppercase tracking-wider ${isSm ? 'px-2' : 'px-3'}`}>
|
|
216
|
-
{item.label}
|
|
217
|
-
</div>
|
|
218
|
-
{item.options.map(renderOption)}
|
|
219
|
-
</div>
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
return renderOption(item);
|
|
223
|
-
})}
|
|
224
|
-
</div>
|
|
225
|
-
)}
|
|
253
|
+
{listPortal}
|
|
226
254
|
</div>
|
|
227
255
|
);
|
|
228
256
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
4
5
|
import { Folder, ChevronDown, ChevronRight, Check } from 'lucide-react';
|
|
5
6
|
import { stripEmoji } from '@/lib/utils';
|
|
6
7
|
|
|
@@ -18,27 +19,44 @@ interface DirPickerProps {
|
|
|
18
19
|
const PANEL_MAX_H = 200;
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
* Hierarchical directory picker —
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* Hierarchical directory picker — trigger button stays in layout flow;
|
|
23
|
+
* the expanded panel renders via portal with position:fixed so it escapes
|
|
24
|
+
* any ancestor overflow:hidden / overflow:auto containers.
|
|
24
25
|
*/
|
|
25
26
|
export default function DirPicker({ dirPaths, value, onChange, rootLabel = 'Root' }: DirPickerProps) {
|
|
26
27
|
const [expanded, setExpanded] = useState(false);
|
|
27
28
|
const [browsing, setBrowsing] = useState(value);
|
|
28
|
-
const [
|
|
29
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
29
|
+
const [panelPos, setPanelPos] = useState<{ top: number; left: number; width: number; flipUp: boolean } | null>(null);
|
|
30
30
|
const btnRef = useRef<HTMLButtonElement>(null);
|
|
31
|
+
const panelRef = useRef<HTMLDivElement>(null);
|
|
31
32
|
|
|
32
33
|
useEffect(() => { setBrowsing(value); }, [value]);
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (!expanded || !btnRef.current) return;
|
|
35
|
+
const calcPosition = useCallback(() => {
|
|
36
|
+
if (!btnRef.current) return;
|
|
37
37
|
const rect = btnRef.current.getBoundingClientRect();
|
|
38
38
|
const spaceBelow = window.innerHeight - rect.bottom;
|
|
39
39
|
const spaceAbove = rect.top;
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const flip = spaceBelow < PANEL_MAX_H + 8 && spaceAbove > spaceBelow;
|
|
41
|
+
setPanelPos({
|
|
42
|
+
top: flip ? rect.top : rect.bottom,
|
|
43
|
+
left: rect.left,
|
|
44
|
+
width: rect.width,
|
|
45
|
+
flipUp: flip,
|
|
46
|
+
});
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
// Recalculate position on open, scroll, and resize
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (!expanded) { setPanelPos(null); return; }
|
|
52
|
+
calcPosition();
|
|
53
|
+
window.addEventListener('scroll', calcPosition, true);
|
|
54
|
+
window.addEventListener('resize', calcPosition);
|
|
55
|
+
return () => {
|
|
56
|
+
window.removeEventListener('scroll', calcPosition, true);
|
|
57
|
+
window.removeEventListener('resize', calcPosition);
|
|
58
|
+
};
|
|
59
|
+
}, [expanded, calcPosition]);
|
|
42
60
|
|
|
43
61
|
const collapse = useCallback(() => setExpanded(false), []);
|
|
44
62
|
|
|
@@ -48,9 +66,12 @@ export default function DirPicker({ dirPaths, value, onChange, rootLabel = 'Root
|
|
|
48
66
|
if (e.key === 'Escape') { e.preventDefault(); collapse(); }
|
|
49
67
|
};
|
|
50
68
|
const handleClick = (e: MouseEvent) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
69
|
+
const target = e.target as Node;
|
|
70
|
+
if (
|
|
71
|
+
btnRef.current?.contains(target) ||
|
|
72
|
+
panelRef.current?.contains(target)
|
|
73
|
+
) return;
|
|
74
|
+
collapse();
|
|
54
75
|
};
|
|
55
76
|
document.addEventListener('keydown', handleKey);
|
|
56
77
|
document.addEventListener('mousedown', handleClick);
|
|
@@ -87,9 +108,82 @@ export default function DirPicker({ dirPaths, value, onChange, rootLabel = 'Root
|
|
|
87
108
|
? value.split('/').map(s => stripEmoji(s)).join(' / ')
|
|
88
109
|
: '/ ' + rootLabel;
|
|
89
110
|
|
|
111
|
+
const panel = expanded && panelPos && createPortal(
|
|
112
|
+
<div
|
|
113
|
+
ref={panelRef}
|
|
114
|
+
className="fixed z-[9999] rounded-lg border border-[var(--amber)] bg-card shadow-lg overflow-hidden flex flex-col"
|
|
115
|
+
style={{
|
|
116
|
+
left: panelPos.left,
|
|
117
|
+
width: panelPos.width,
|
|
118
|
+
maxHeight: PANEL_MAX_H,
|
|
119
|
+
...(panelPos.flipUp
|
|
120
|
+
? { bottom: window.innerHeight - panelPos.top + 4 }
|
|
121
|
+
: { top: panelPos.top + 4 }),
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
{/* Breadcrumb */}
|
|
125
|
+
<div className="flex items-center gap-0.5 px-3 py-1.5 bg-muted/30 border-b border-border overflow-x-auto text-xs shrink-0">
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
onClick={() => navigateTo(-1)}
|
|
129
|
+
className={`shrink-0 px-1.5 py-0.5 rounded transition-colors ${
|
|
130
|
+
browsing === '' ? 'text-[var(--amber)] font-medium' : 'text-muted-foreground hover:text-foreground'
|
|
131
|
+
}`}
|
|
132
|
+
>
|
|
133
|
+
/ {rootLabel}
|
|
134
|
+
</button>
|
|
135
|
+
{segments.map((seg, i) => (
|
|
136
|
+
<span key={i} className="flex items-center gap-0.5 shrink-0">
|
|
137
|
+
<ChevronRight size={10} className="text-muted-foreground/50" />
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
onClick={() => navigateTo(i)}
|
|
141
|
+
className={`px-1.5 py-0.5 rounded transition-colors truncate max-w-[100px] ${
|
|
142
|
+
i === segments.length - 1 ? 'text-[var(--amber)] font-medium' : 'text-muted-foreground hover:text-foreground'
|
|
143
|
+
}`}
|
|
144
|
+
>
|
|
145
|
+
{seg}
|
|
146
|
+
</button>
|
|
147
|
+
</span>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
{/* Child directories */}
|
|
151
|
+
{children.length > 0 ? (
|
|
152
|
+
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
153
|
+
{children.map(childPath => {
|
|
154
|
+
const childName = childPath.split('/').pop() || childPath;
|
|
155
|
+
const hasChildren = dirPaths.some(p => p.startsWith(childPath + '/'));
|
|
156
|
+
return (
|
|
157
|
+
<button
|
|
158
|
+
key={childPath}
|
|
159
|
+
type="button"
|
|
160
|
+
onClick={() => drillInto(childPath)}
|
|
161
|
+
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-foreground hover:bg-muted/60 transition-colors"
|
|
162
|
+
>
|
|
163
|
+
<Folder size={12} className="shrink-0 text-[var(--amber)]" />
|
|
164
|
+
<span className="flex-1 text-left truncate">{childName}</span>
|
|
165
|
+
{hasChildren && <ChevronRight size={11} className="shrink-0 text-muted-foreground/40" />}
|
|
166
|
+
</button>
|
|
167
|
+
);
|
|
168
|
+
})}
|
|
169
|
+
</div>
|
|
170
|
+
) : (
|
|
171
|
+
<div className="px-3 py-2 text-xs text-muted-foreground/50 text-center">—</div>
|
|
172
|
+
)}
|
|
173
|
+
{/* Confirm & collapse */}
|
|
174
|
+
<button
|
|
175
|
+
type="button"
|
|
176
|
+
onClick={collapse}
|
|
177
|
+
className="w-full py-1.5 flex items-center justify-center gap-1 text-xs font-medium text-[var(--amber)] border-t border-border hover:bg-muted/30 transition-colors shrink-0"
|
|
178
|
+
>
|
|
179
|
+
<Check size={12} />
|
|
180
|
+
</button>
|
|
181
|
+
</div>,
|
|
182
|
+
document.body,
|
|
183
|
+
);
|
|
184
|
+
|
|
90
185
|
return (
|
|
91
|
-
|
|
92
|
-
{/* Trigger — always in document flow */}
|
|
186
|
+
<>
|
|
93
187
|
<button
|
|
94
188
|
ref={btnRef}
|
|
95
189
|
type="button"
|
|
@@ -107,71 +201,7 @@ export default function DirPicker({ dirPaths, value, onChange, rootLabel = 'Root
|
|
|
107
201
|
className={`shrink-0 text-muted-foreground transition-transform duration-150 ${expanded ? 'rotate-180' : ''}`}
|
|
108
202
|
/>
|
|
109
203
|
</button>
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
{expanded && (
|
|
113
|
-
<div className={`absolute z-50 left-0 right-0 rounded-lg border border-[var(--amber)] bg-card shadow-lg overflow-hidden max-h-[200px] flex flex-col ${
|
|
114
|
-
flipUp ? 'bottom-full mb-1' : 'top-full mt-1'
|
|
115
|
-
}`}>
|
|
116
|
-
{/* Breadcrumb */}
|
|
117
|
-
<div className="flex items-center gap-0.5 px-3 py-1.5 bg-muted/30 border-b border-border overflow-x-auto text-xs shrink-0">
|
|
118
|
-
<button
|
|
119
|
-
type="button"
|
|
120
|
-
onClick={() => navigateTo(-1)}
|
|
121
|
-
className={`shrink-0 px-1.5 py-0.5 rounded transition-colors ${
|
|
122
|
-
browsing === '' ? 'text-[var(--amber)] font-medium' : 'text-muted-foreground hover:text-foreground'
|
|
123
|
-
}`}
|
|
124
|
-
>
|
|
125
|
-
/ {rootLabel}
|
|
126
|
-
</button>
|
|
127
|
-
{segments.map((seg, i) => (
|
|
128
|
-
<span key={i} className="flex items-center gap-0.5 shrink-0">
|
|
129
|
-
<ChevronRight size={10} className="text-muted-foreground/50" />
|
|
130
|
-
<button
|
|
131
|
-
type="button"
|
|
132
|
-
onClick={() => navigateTo(i)}
|
|
133
|
-
className={`px-1.5 py-0.5 rounded transition-colors truncate max-w-[100px] ${
|
|
134
|
-
i === segments.length - 1 ? 'text-[var(--amber)] font-medium' : 'text-muted-foreground hover:text-foreground'
|
|
135
|
-
}`}
|
|
136
|
-
>
|
|
137
|
-
{seg}
|
|
138
|
-
</button>
|
|
139
|
-
</span>
|
|
140
|
-
))}
|
|
141
|
-
</div>
|
|
142
|
-
{/* Child directories */}
|
|
143
|
-
{children.length > 0 ? (
|
|
144
|
-
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
145
|
-
{children.map(childPath => {
|
|
146
|
-
const childName = childPath.split('/').pop() || childPath;
|
|
147
|
-
const hasChildren = dirPaths.some(p => p.startsWith(childPath + '/'));
|
|
148
|
-
return (
|
|
149
|
-
<button
|
|
150
|
-
key={childPath}
|
|
151
|
-
type="button"
|
|
152
|
-
onClick={() => drillInto(childPath)}
|
|
153
|
-
className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-foreground hover:bg-muted/60 transition-colors"
|
|
154
|
-
>
|
|
155
|
-
<Folder size={12} className="shrink-0 text-[var(--amber)]" />
|
|
156
|
-
<span className="flex-1 text-left truncate">{childName}</span>
|
|
157
|
-
{hasChildren && <ChevronRight size={11} className="shrink-0 text-muted-foreground/40" />}
|
|
158
|
-
</button>
|
|
159
|
-
);
|
|
160
|
-
})}
|
|
161
|
-
</div>
|
|
162
|
-
) : (
|
|
163
|
-
<div className="px-3 py-2 text-xs text-muted-foreground/50 text-center">—</div>
|
|
164
|
-
)}
|
|
165
|
-
{/* Confirm & collapse */}
|
|
166
|
-
<button
|
|
167
|
-
type="button"
|
|
168
|
-
onClick={collapse}
|
|
169
|
-
className="w-full py-1.5 flex items-center justify-center gap-1 text-xs font-medium text-[var(--amber)] border-t border-border hover:bg-muted/30 transition-colors shrink-0"
|
|
170
|
-
>
|
|
171
|
-
<Check size={12} />
|
|
172
|
-
</button>
|
|
173
|
-
</div>
|
|
174
|
-
)}
|
|
175
|
-
</div>
|
|
204
|
+
{panel}
|
|
205
|
+
</>
|
|
176
206
|
);
|
|
177
207
|
}
|
|
@@ -73,11 +73,18 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles,
|
|
|
73
73
|
useEffect(() => {
|
|
74
74
|
if (!open) return;
|
|
75
75
|
const handler = (e: KeyboardEvent) => {
|
|
76
|
-
if (e.key
|
|
76
|
+
if (e.key !== 'Escape') return;
|
|
77
|
+
if (showDiscard) {
|
|
78
|
+
e.stopPropagation();
|
|
79
|
+
setShowDiscard(false);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
e.stopPropagation();
|
|
83
|
+
handleClose();
|
|
77
84
|
};
|
|
78
85
|
window.addEventListener('keydown', handler, true);
|
|
79
86
|
return () => window.removeEventListener('keydown', handler, true);
|
|
80
|
-
}, [open, handleClose]);
|
|
87
|
+
}, [open, handleClose, showDiscard]);
|
|
81
88
|
|
|
82
89
|
const checkConflicts = useCallback(async (fileNames: string[], space: string) => {
|
|
83
90
|
try {
|
|
@@ -197,15 +204,6 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles,
|
|
|
197
204
|
|
|
198
205
|
return (
|
|
199
206
|
<>
|
|
200
|
-
<ConfirmDialog
|
|
201
|
-
open={showDiscard}
|
|
202
|
-
title={t.fileImport.discardTitle}
|
|
203
|
-
message={t.fileImport.discardMessage(im.files.length)}
|
|
204
|
-
confirmLabel={t.fileImport.discardConfirm}
|
|
205
|
-
cancelLabel={t.fileImport.discardCancel}
|
|
206
|
-
onConfirm={doClose}
|
|
207
|
-
onCancel={() => setShowDiscard(false)}
|
|
208
|
-
/>
|
|
209
207
|
<div
|
|
210
208
|
ref={overlayRef}
|
|
211
209
|
className={`fixed inset-0 z-50 modal-backdrop flex items-center justify-center p-4 transition-opacity duration-200 ${closing ? 'opacity-0' : 'opacity-100'}`}
|
|
@@ -507,6 +505,15 @@ export default function ImportModal({ open, onClose, defaultSpace, initialFiles,
|
|
|
507
505
|
</div>
|
|
508
506
|
</div>
|
|
509
507
|
</div>
|
|
508
|
+
<ConfirmDialog
|
|
509
|
+
open={showDiscard}
|
|
510
|
+
title={t.fileImport.discardTitle}
|
|
511
|
+
message={t.fileImport.discardMessage(im.files.length)}
|
|
512
|
+
confirmLabel={t.fileImport.discardConfirm}
|
|
513
|
+
cancelLabel={t.fileImport.discardCancel}
|
|
514
|
+
onConfirm={doClose}
|
|
515
|
+
onCancel={() => setShowDiscard(false)}
|
|
516
|
+
/>
|
|
510
517
|
</>
|
|
511
518
|
);
|
|
512
519
|
}
|
|
@@ -16,11 +16,8 @@ export default function JsonView({ content }: JsonViewProps) {
|
|
|
16
16
|
}, [content]);
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
|
-
<pre
|
|
20
|
-
|
|
21
|
-
suppressHydrationWarning
|
|
22
|
-
>
|
|
23
|
-
<code>{pretty}</code>
|
|
19
|
+
<pre className="rounded-xl border border-border bg-card px-4 py-3 overflow-x-auto text-sm leading-relaxed font-display">
|
|
20
|
+
<code suppressHydrationWarning>{pretty}</code>
|
|
24
21
|
</pre>
|
|
25
22
|
);
|
|
26
23
|
}
|
package/app/components/Panel.tsx
CHANGED
|
@@ -345,12 +345,22 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
345
345
|
<ActivityBar
|
|
346
346
|
activePanel={railActivePanel}
|
|
347
347
|
onPanelChange={lp.setActivePanel}
|
|
348
|
+
onEchoClick={() => {
|
|
349
|
+
const wasActive = lp.activePanel === 'echo';
|
|
350
|
+
lp.setActivePanel(wasActive ? null : 'echo');
|
|
351
|
+
if (!wasActive) router.push('/echo/about-you');
|
|
352
|
+
}}
|
|
348
353
|
onAgentsClick={() => {
|
|
349
354
|
const wasActive = lp.activePanel === 'agents';
|
|
350
355
|
lp.setActivePanel(wasActive ? null : 'agents');
|
|
351
356
|
if (!wasActive) router.push('/agents');
|
|
352
357
|
setAgentDetailKey(null);
|
|
353
358
|
}}
|
|
359
|
+
onDiscoverClick={() => {
|
|
360
|
+
const wasActive = lp.activePanel === 'discover';
|
|
361
|
+
lp.setActivePanel(wasActive ? null : 'discover');
|
|
362
|
+
if (!wasActive) router.push('/explore');
|
|
363
|
+
}}
|
|
354
364
|
syncStatus={syncStatus}
|
|
355
365
|
expanded={lp.railExpanded}
|
|
356
366
|
onExpandedChange={handleExpandedChange}
|
|
@@ -97,21 +97,21 @@ export default function UpdateOverlay() {
|
|
|
97
97
|
>
|
|
98
98
|
{done ? (
|
|
99
99
|
<>
|
|
100
|
-
<CheckCircle2 size={32} style={{ color: '
|
|
101
|
-
<div style={{ color: '
|
|
100
|
+
<CheckCircle2 size={32} style={{ color: 'var(--success)', marginBottom: 12 }} />
|
|
101
|
+
<div style={{ color: 'var(--foreground)', fontSize: 18, fontWeight: 600 }}>
|
|
102
102
|
{zh ? '更新成功!' : 'Update Complete!'}
|
|
103
103
|
</div>
|
|
104
|
-
<div style={{ color: '
|
|
104
|
+
<div style={{ color: 'var(--muted-foreground)', fontSize: 13, marginTop: 6 }}>
|
|
105
105
|
{zh ? '正在刷新页面...' : 'Reloading...'}
|
|
106
106
|
</div>
|
|
107
107
|
</>
|
|
108
108
|
) : (
|
|
109
109
|
<>
|
|
110
|
-
<Loader2 size={32} style={{ color: '
|
|
111
|
-
<div style={{ color: '
|
|
110
|
+
<Loader2 size={32} style={{ color: 'var(--amber)', marginBottom: 12, animation: 'spin 1s linear infinite' }} />
|
|
111
|
+
<div style={{ color: 'var(--foreground)', fontSize: 18, fontWeight: 600 }}>
|
|
112
112
|
{zh ? 'MindOS 正在更新...' : 'MindOS is Updating...'}
|
|
113
113
|
</div>
|
|
114
|
-
<div style={{ color: '
|
|
114
|
+
<div style={{ color: 'var(--muted-foreground)', fontSize: 13, marginTop: 6, textAlign: 'center', maxWidth: 300, lineHeight: 1.5 }}>
|
|
115
115
|
{zh
|
|
116
116
|
? '服务正在重启,请勿关闭此页面。完成后将自动刷新。'
|
|
117
117
|
: 'The server is restarting. Please do not close this page. It will auto-reload when ready.'}
|
|
@@ -243,9 +243,9 @@ export default function AgentDetailContent({ agentKey }: { agentKey: string }) {
|
|
|
243
243
|
<h1 className="text-lg font-semibold tracking-tight font-display text-foreground truncate">{agent.name}</h1>
|
|
244
244
|
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 mt-0.5 text-2xs text-muted-foreground/60">
|
|
245
245
|
<span className={`font-medium px-1.5 py-px rounded-full ${
|
|
246
|
-
status === 'connected' ? 'bg-
|
|
246
|
+
status === 'connected' ? 'bg-muted text-muted-foreground'
|
|
247
247
|
: status === 'detected' ? 'bg-[var(--amber-subtle)] text-[var(--amber-text)]'
|
|
248
|
-
: 'bg-
|
|
248
|
+
: 'bg-error/10 text-error'
|
|
249
249
|
}`}>{status}</span>
|
|
250
250
|
<span className="font-mono">{agent.transport ?? agent.preferredTransport}</span>
|
|
251
251
|
<span className="text-muted-foreground/25" aria-hidden="true">·</span>
|
|
@@ -296,7 +296,7 @@ function ByAgentView({
|
|
|
296
296
|
const mcpServers = agent.configuredMcpServers ?? [];
|
|
297
297
|
const nativeSkillCount = (agent.installedSkillNames ?? []).length;
|
|
298
298
|
return (
|
|
299
|
-
<div key={agent.key} className={`rounded-xl border bg-card group hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-150 ${status === '
|
|
299
|
+
<div key={agent.key} className={`rounded-xl border bg-card group hover:shadow-[0_2px_8px_rgba(0,0,0,0.04)] transition-all duration-150 ${status === 'detected' ? 'border-l-2 border-l-[var(--amber)] border-border' : status === 'notFound' ? 'border-l-2 border-l-error border-border' : 'border-border'}`}>
|
|
300
300
|
{/* Card header with avatar */}
|
|
301
301
|
<div className="flex items-center gap-3 p-3">
|
|
302
302
|
<AgentAvatar name={agent.name} status={status} />
|
|
@@ -306,7 +306,7 @@ function ByAgentView({
|
|
|
306
306
|
{agent.name}
|
|
307
307
|
</Link>
|
|
308
308
|
<span className="text-2xs text-muted-foreground font-mono shrink-0">{agent.transport ?? agent.preferredTransport}</span>
|
|
309
|
-
<span className={`text-2xs px-1.5 py-0.5 rounded shrink-0 ${status === 'connected' ? 'bg-
|
|
309
|
+
<span className={`text-2xs px-1.5 py-0.5 rounded shrink-0 ${status === 'connected' ? 'bg-muted text-muted-foreground' : status === 'detected' ? 'bg-[var(--amber-dim)] text-[var(--amber-text)]' : 'bg-error/10 text-error'}`}>
|
|
310
310
|
{copy.status[status]}
|
|
311
311
|
</span>
|
|
312
312
|
</div>
|
|
@@ -269,15 +269,15 @@ function StatCell({
|
|
|
269
269
|
: 'text-muted-foreground';
|
|
270
270
|
const iconColor =
|
|
271
271
|
tone === 'ok'
|
|
272
|
-
? 'text-
|
|
272
|
+
? 'text-[var(--success)]/70'
|
|
273
273
|
: tone === 'warn'
|
|
274
|
-
? 'text-amber
|
|
274
|
+
? 'text-[var(--amber)]/70'
|
|
275
275
|
: 'text-muted-foreground/40';
|
|
276
276
|
const hoverBg =
|
|
277
277
|
tone === 'ok'
|
|
278
|
-
? 'hover:bg-
|
|
278
|
+
? 'hover:bg-muted/20'
|
|
279
279
|
: tone === 'warn'
|
|
280
|
-
? 'hover:bg-amber
|
|
280
|
+
? 'hover:bg-[var(--amber)]/[0.04]'
|
|
281
281
|
: 'hover:bg-muted/20';
|
|
282
282
|
|
|
283
283
|
return (
|
|
@@ -335,8 +335,8 @@ function QuickNavCard({
|
|
|
335
335
|
<span
|
|
336
336
|
className={`text-2xs px-2 py-0.5 rounded-full font-medium select-none ${
|
|
337
337
|
statTone === 'ok'
|
|
338
|
-
? 'bg-
|
|
339
|
-
: 'bg-amber-
|
|
338
|
+
? 'bg-muted text-muted-foreground'
|
|
339
|
+
: 'bg-[var(--amber-dim)] text-[var(--amber-text)]'
|
|
340
340
|
}`}
|
|
341
341
|
>
|
|
342
342
|
{stat}
|
|
@@ -373,10 +373,10 @@ function AgentCard({
|
|
|
373
373
|
status === 'connected' ? copy.connected : status === 'detected' ? copy.detected : copy.notFound;
|
|
374
374
|
const statusColor =
|
|
375
375
|
status === 'connected'
|
|
376
|
-
? 'bg-
|
|
376
|
+
? 'bg-muted text-muted-foreground'
|
|
377
377
|
: status === 'detected'
|
|
378
|
-
? 'bg-amber-
|
|
379
|
-
: 'bg-
|
|
378
|
+
? 'bg-[var(--amber-dim)] text-[var(--amber-text)]'
|
|
379
|
+
: 'bg-error/10 text-error';
|
|
380
380
|
|
|
381
381
|
return (
|
|
382
382
|
<Link
|
|
@@ -410,10 +410,10 @@ function AgentCard({
|
|
|
410
410
|
<span className="flex-1 min-w-[4px]" />
|
|
411
411
|
{hasRuntime && (
|
|
412
412
|
<span
|
|
413
|
-
className="flex items-center gap-1 text-
|
|
413
|
+
className="flex items-center gap-1 text-[var(--success)]"
|
|
414
414
|
title={copy.runtimeActive}
|
|
415
415
|
>
|
|
416
|
-
<span className="w-1.5 h-1.5 rounded-full bg-
|
|
416
|
+
<span className="w-1.5 h-1.5 rounded-full bg-[var(--success)] animate-pulse" aria-hidden="true" />
|
|
417
417
|
<span className="text-2xs font-medium">{copy.runtimeActive}</span>
|
|
418
418
|
</span>
|
|
419
419
|
)}
|
|
@@ -27,7 +27,7 @@ export function StatusDot({ tone, label, count }: { tone: 'ok' | 'warn' | 'neutr
|
|
|
27
27
|
const countCls = tone === 'ok' ? 'text-foreground' : tone === 'warn' ? 'text-[var(--amber)]' : 'text-muted-foreground';
|
|
28
28
|
return (
|
|
29
29
|
<span className="inline-flex items-center gap-1.5 text-muted-foreground">
|
|
30
|
-
<span className={`w-2 h-2 rounded-full ${dotCls}
|
|
30
|
+
<span className={`w-2 h-2 rounded-full ${dotCls}`} aria-hidden="true" />
|
|
31
31
|
<span className="text-xs">{label}</span>
|
|
32
32
|
<span className={`tabular-nums font-medium ${countCls}`}>{count}</span>
|
|
33
33
|
</span>
|
|
@@ -131,20 +131,20 @@ export function EmptyState({ message, icon, className }: { message: string; icon
|
|
|
131
131
|
|
|
132
132
|
/* ────────── Agent Avatar ────────── */
|
|
133
133
|
|
|
134
|
-
/**
|
|
134
|
+
/** Dual-mode palette: soft pastels in light, muted tones in dark — [bg, border, text] */
|
|
135
135
|
const AVATAR_PALETTES: [string, string, string][] = [
|
|
136
|
-
['bg-rose-100/70',
|
|
137
|
-
['bg-violet-100/70', 'border-violet-300/50', 'text-violet-600/80'],
|
|
138
|
-
['bg-emerald-100/70',
|
|
139
|
-
['bg-sky-100/70',
|
|
140
|
-
['bg-amber-100/70',
|
|
141
|
-
['bg-teal-100/70',
|
|
142
|
-
['bg-pink-100/70',
|
|
143
|
-
['bg-indigo-100/70', 'border-indigo-300/50', 'text-indigo-600/80'],
|
|
144
|
-
['bg-lime-100/70',
|
|
145
|
-
['bg-fuchsia-100/70',
|
|
146
|
-
['bg-cyan-100/70',
|
|
147
|
-
['bg-orange-100/70', 'border-orange-300/50', 'text-orange-600/80'],
|
|
136
|
+
['bg-rose-100/70 dark:bg-rose-900/30', 'border-rose-300/50 dark:border-rose-700/40', 'text-rose-600/80 dark:text-rose-400/80'],
|
|
137
|
+
['bg-violet-100/70 dark:bg-violet-900/30', 'border-violet-300/50 dark:border-violet-700/40', 'text-violet-600/80 dark:text-violet-400/80'],
|
|
138
|
+
['bg-emerald-100/70 dark:bg-emerald-900/30', 'border-emerald-300/50 dark:border-emerald-700/40','text-emerald-600/80 dark:text-emerald-400/80'],
|
|
139
|
+
['bg-sky-100/70 dark:bg-sky-900/30', 'border-sky-300/50 dark:border-sky-700/40', 'text-sky-600/80 dark:text-sky-400/80'],
|
|
140
|
+
['bg-amber-100/70 dark:bg-amber-900/30', 'border-amber-300/50 dark:border-amber-700/40', 'text-amber-700/80 dark:text-amber-400/80'],
|
|
141
|
+
['bg-teal-100/70 dark:bg-teal-900/30', 'border-teal-300/50 dark:border-teal-700/40', 'text-teal-600/80 dark:text-teal-400/80'],
|
|
142
|
+
['bg-pink-100/70 dark:bg-pink-900/30', 'border-pink-300/50 dark:border-pink-700/40', 'text-pink-600/80 dark:text-pink-400/80'],
|
|
143
|
+
['bg-indigo-100/70 dark:bg-indigo-900/30', 'border-indigo-300/50 dark:border-indigo-700/40', 'text-indigo-600/80 dark:text-indigo-400/80'],
|
|
144
|
+
['bg-lime-100/70 dark:bg-lime-900/30', 'border-lime-300/50 dark:border-lime-700/40', 'text-lime-700/80 dark:text-lime-400/80'],
|
|
145
|
+
['bg-fuchsia-100/70 dark:bg-fuchsia-900/30', 'border-fuchsia-300/50 dark:border-fuchsia-700/40','text-fuchsia-600/80 dark:text-fuchsia-400/80'],
|
|
146
|
+
['bg-cyan-100/70 dark:bg-cyan-900/30', 'border-cyan-300/50 dark:border-cyan-700/40', 'text-cyan-600/80 dark:text-cyan-400/80'],
|
|
147
|
+
['bg-orange-100/70 dark:bg-orange-900/30', 'border-orange-300/50 dark:border-orange-700/40', 'text-orange-600/80 dark:text-orange-400/80'],
|
|
148
148
|
];
|
|
149
149
|
|
|
150
150
|
function hashName(str: string): number {
|
|
@@ -696,7 +696,7 @@ function AgentCard({
|
|
|
696
696
|
</Link>
|
|
697
697
|
{skillMode && (
|
|
698
698
|
<span className={`text-2xs px-1.5 py-0.5 rounded shrink-0 ${
|
|
699
|
-
skillMode === 'universal' ? 'bg-
|
|
699
|
+
skillMode === 'universal' ? 'bg-muted text-muted-foreground'
|
|
700
700
|
: skillMode === 'additional' ? 'bg-[var(--amber-dim)] text-[var(--amber-text)]'
|
|
701
701
|
: 'bg-muted text-muted-foreground'
|
|
702
702
|
}`}>
|
|
@@ -422,7 +422,7 @@ function MetaCard({
|
|
|
422
422
|
tone?: 'ok' | 'muted' | 'default';
|
|
423
423
|
}) {
|
|
424
424
|
const valueColor =
|
|
425
|
-
tone === 'ok' ? 'text-
|
|
425
|
+
tone === 'ok' ? 'text-[var(--success)]'
|
|
426
426
|
: tone === 'muted' ? 'text-muted-foreground'
|
|
427
427
|
: 'text-foreground';
|
|
428
428
|
return (
|
|
@@ -18,7 +18,7 @@ function UserMessageContent({ content, skillName }: { content: string; skillName
|
|
|
18
18
|
const rest = prefixMatch ? content.slice(prefixMatch[0].length) : content;
|
|
19
19
|
return (
|
|
20
20
|
<>
|
|
21
|
-
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[11px] font-medium bg-[var(--amber
|
|
21
|
+
<span className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-[11px] font-medium bg-[var(--amber)]/15 text-[var(--amber)] mr-1 align-middle">
|
|
22
22
|
<Zap size={10} className="shrink-0" />
|
|
23
23
|
{resolved}
|
|
24
24
|
</span>
|
|
@@ -19,12 +19,12 @@ export default function EchoPanel({ active, maximized, onMaximize }: EchoPanelPr
|
|
|
19
19
|
const e = t.panels.echo;
|
|
20
20
|
const pathname = usePathname() ?? '';
|
|
21
21
|
|
|
22
|
-
const rowBySegment: Record<EchoSegment, { icon: ReactNode; title: string
|
|
23
|
-
'about-you': { icon: <UserRound size={14} />, title: e.aboutYouTitle
|
|
24
|
-
continued: { icon: <Bookmark size={14} />, title: e.continuedTitle
|
|
25
|
-
daily: { icon: <Sun size={14} />, title: e.dailyEchoTitle
|
|
26
|
-
'past-you': { icon: <History size={14} />, title: e.pastYouTitle
|
|
27
|
-
growth: { icon: <Brain size={14} />, title: e.intentGrowthTitle
|
|
22
|
+
const rowBySegment: Record<EchoSegment, { icon: ReactNode; title: string }> = {
|
|
23
|
+
'about-you': { icon: <UserRound size={14} />, title: e.aboutYouTitle },
|
|
24
|
+
continued: { icon: <Bookmark size={14} />, title: e.continuedTitle },
|
|
25
|
+
daily: { icon: <Sun size={14} />, title: e.dailyEchoTitle },
|
|
26
|
+
'past-you': { icon: <History size={14} />, title: e.pastYouTitle },
|
|
27
|
+
growth: { icon: <Brain size={14} />, title: e.intentGrowthTitle },
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
return (
|
|
@@ -37,7 +37,7 @@ export default function EchoPanel({ active, maximized, onMaximize }: EchoPanelPr
|
|
|
37
37
|
const href = ECHO_SEGMENT_HREF[segment];
|
|
38
38
|
const isActive = pathname === href || pathname.startsWith(`${href}/`);
|
|
39
39
|
return (
|
|
40
|
-
<PanelNavRow key={segment} href={href} icon={row.icon} title={row.title}
|
|
40
|
+
<PanelNavRow key={segment} href={href} icon={row.icon} title={row.title} active={isActive} />
|
|
41
41
|
);
|
|
42
42
|
})}
|
|
43
43
|
</div>
|
|
@@ -207,7 +207,7 @@ Be specific. Reference actual content from the files. Keep the total response un
|
|
|
207
207
|
|
|
208
208
|
{/* error */}
|
|
209
209
|
{error && (
|
|
210
|
-
<div className="font-display" style={{ padding: '10px 14px', borderRadius: 8, background: '
|
|
210
|
+
<div className="font-display" style={{ padding: '10px 14px', borderRadius: 8, background: 'color-mix(in srgb, var(--error) 10%, transparent)', border: '1px solid color-mix(in srgb, var(--error) 30%, transparent)', color: 'var(--error)', fontSize: 12, marginBottom: '1rem' }}>
|
|
211
211
|
{error}
|
|
212
212
|
</div>
|
|
213
213
|
)}
|
package/package.json
CHANGED