@geminilight/mindos 0.5.20 → 0.5.22
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/api/ask/route.ts +343 -178
- package/app/app/api/monitoring/route.ts +95 -0
- package/app/components/SettingsModal.tsx +58 -58
- package/app/components/settings/AgentsTab.tsx +240 -0
- package/app/components/settings/AiTab.tsx +4 -25
- package/app/components/settings/AppearanceTab.tsx +31 -13
- package/app/components/settings/KnowledgeTab.tsx +13 -28
- package/app/components/settings/McpAgentInstall.tsx +227 -0
- package/app/components/settings/McpServerStatus.tsx +172 -0
- package/app/components/settings/McpSkillsSection.tsx +583 -0
- package/app/components/settings/McpTab.tsx +17 -959
- package/app/components/settings/MonitoringTab.tsx +202 -0
- package/app/components/settings/PluginsTab.tsx +4 -27
- package/app/components/settings/Primitives.tsx +69 -0
- package/app/components/settings/ShortcutsTab.tsx +2 -4
- package/app/components/settings/SyncTab.tsx +8 -24
- package/app/components/settings/types.ts +116 -2
- package/app/instrumentation.ts +7 -2
- package/app/lib/agent/context.ts +151 -87
- package/app/lib/agent/index.ts +5 -3
- package/app/lib/agent/log.ts +1 -0
- package/app/lib/agent/model.ts +76 -10
- package/app/lib/agent/skill-rules.ts +70 -0
- package/app/lib/agent/stream-consumer.ts +73 -77
- package/app/lib/agent/to-agent-messages.ts +106 -0
- package/app/lib/agent/tools.ts +260 -266
- package/app/lib/api.ts +12 -3
- package/app/lib/core/csv.ts +2 -1
- package/app/lib/core/fs-ops.ts +7 -6
- package/app/lib/core/index.ts +1 -1
- package/app/lib/core/lines.ts +7 -6
- package/app/lib/core/search-index.ts +174 -0
- package/app/lib/core/search.ts +30 -1
- package/app/lib/core/security.ts +6 -3
- package/app/lib/errors.ts +108 -0
- package/app/lib/fs.ts +6 -3
- package/app/lib/i18n-en.ts +523 -0
- package/app/lib/i18n-zh.ts +548 -0
- package/app/lib/i18n.ts +4 -963
- package/app/lib/metrics.ts +81 -0
- package/app/next-env.d.ts +1 -1
- package/app/next.config.ts +1 -1
- package/app/package-lock.json +3258 -3093
- package/app/package.json +6 -3
- package/bin/cli.js +7 -4
- package/package.json +4 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from 'react';
|
|
4
|
+
import { Activity, Cpu, Database, HardDrive, Loader2, RefreshCw, Zap } from 'lucide-react';
|
|
5
|
+
import { apiFetch } from '@/lib/api';
|
|
6
|
+
import type { Messages } from '@/lib/i18n';
|
|
7
|
+
|
|
8
|
+
interface MonitoringData {
|
|
9
|
+
system: {
|
|
10
|
+
uptimeMs: number;
|
|
11
|
+
memory: { heapUsed: number; heapTotal: number; rss: number };
|
|
12
|
+
nodeVersion: string;
|
|
13
|
+
};
|
|
14
|
+
application: {
|
|
15
|
+
agentRequests: number;
|
|
16
|
+
toolExecutions: number;
|
|
17
|
+
totalTokens: { input: number; output: number };
|
|
18
|
+
avgResponseTimeMs: number;
|
|
19
|
+
errors: number;
|
|
20
|
+
};
|
|
21
|
+
knowledgeBase: {
|
|
22
|
+
root: string;
|
|
23
|
+
fileCount: number;
|
|
24
|
+
totalSizeBytes: number;
|
|
25
|
+
};
|
|
26
|
+
mcp: {
|
|
27
|
+
running: boolean;
|
|
28
|
+
port: number;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function formatBytes(bytes: number): string {
|
|
33
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
34
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
35
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
36
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function formatUptime(ms: number): string {
|
|
40
|
+
const s = Math.floor(ms / 1000);
|
|
41
|
+
if (s < 60) return `${s}s`;
|
|
42
|
+
const m = Math.floor(s / 60);
|
|
43
|
+
if (m < 60) return `${m}m ${s % 60}s`;
|
|
44
|
+
const h = Math.floor(m / 60);
|
|
45
|
+
if (h < 24) return `${h}h ${m % 60}m`;
|
|
46
|
+
const d = Math.floor(h / 24);
|
|
47
|
+
return `${d}d ${h % 24}h`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function ProgressBar({ value, max, className }: { value: number; max: number; className?: string }) {
|
|
51
|
+
const pct = max > 0 ? Math.min(100, (value / max) * 100) : 0;
|
|
52
|
+
return (
|
|
53
|
+
<div className={`h-2 w-full rounded-full bg-muted ${className ?? ''}`}>
|
|
54
|
+
<div
|
|
55
|
+
className={`h-full rounded-full transition-all duration-300 ${pct > 85 ? 'bg-destructive' : 'bg-amber-500'}`}
|
|
56
|
+
style={{ width: `${pct}%` }}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function StatCard({ label, value, sub }: { label: string; value: string | number; sub?: string }) {
|
|
63
|
+
return (
|
|
64
|
+
<div className="flex flex-col gap-0.5">
|
|
65
|
+
<span className="text-xs text-muted-foreground">{label}</span>
|
|
66
|
+
<span className="text-sm font-medium tabular-nums">{value}</span>
|
|
67
|
+
{sub && <span className="text-[10px] text-muted-foreground">{sub}</span>}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface MonitoringTabProps {
|
|
73
|
+
t: Messages;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function MonitoringTab({ t }: MonitoringTabProps) {
|
|
77
|
+
const [data, setData] = useState<MonitoringData | null>(null);
|
|
78
|
+
const [loading, setLoading] = useState(true);
|
|
79
|
+
const [error, setError] = useState(false);
|
|
80
|
+
|
|
81
|
+
const mon = t.settings.monitoring;
|
|
82
|
+
|
|
83
|
+
const fetchData = useCallback(async () => {
|
|
84
|
+
try {
|
|
85
|
+
const d = await apiFetch<MonitoringData>('/api/monitoring', { timeout: 5000 });
|
|
86
|
+
setData(d);
|
|
87
|
+
setError(false);
|
|
88
|
+
} catch {
|
|
89
|
+
setError(true);
|
|
90
|
+
} finally {
|
|
91
|
+
setLoading(false);
|
|
92
|
+
}
|
|
93
|
+
}, []);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
fetchData();
|
|
97
|
+
const id = setInterval(fetchData, 5000);
|
|
98
|
+
return () => clearInterval(id);
|
|
99
|
+
}, [fetchData]);
|
|
100
|
+
|
|
101
|
+
if (loading && !data) {
|
|
102
|
+
return (
|
|
103
|
+
<div className="flex justify-center py-8">
|
|
104
|
+
<Loader2 size={18} className="animate-spin text-muted-foreground" />
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (error && !data) {
|
|
110
|
+
return (
|
|
111
|
+
<div className="text-center py-8 text-sm text-muted-foreground">
|
|
112
|
+
{mon.fetchError || 'Failed to load monitoring data'}
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!data) return null;
|
|
118
|
+
|
|
119
|
+
const { system, application, knowledgeBase, mcp } = data;
|
|
120
|
+
const heapPct = system.memory.heapTotal > 0
|
|
121
|
+
? Math.round((system.memory.heapUsed / system.memory.heapTotal) * 100)
|
|
122
|
+
: 0;
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div className="space-y-6">
|
|
126
|
+
{/* System */}
|
|
127
|
+
<section>
|
|
128
|
+
<h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground mb-3">
|
|
129
|
+
<Cpu size={13} className="text-muted-foreground" />
|
|
130
|
+
{mon.system || 'System'}
|
|
131
|
+
</h3>
|
|
132
|
+
<div className="space-y-3">
|
|
133
|
+
<div>
|
|
134
|
+
<div className="flex justify-between text-xs mb-1">
|
|
135
|
+
<span className="text-muted-foreground">{mon.heapMemory || 'Heap Memory'}</span>
|
|
136
|
+
<span className="tabular-nums">{formatBytes(system.memory.heapUsed)} / {formatBytes(system.memory.heapTotal)} ({heapPct}%)</span>
|
|
137
|
+
</div>
|
|
138
|
+
<ProgressBar value={system.memory.heapUsed} max={system.memory.heapTotal} />
|
|
139
|
+
</div>
|
|
140
|
+
<div className="grid grid-cols-3 gap-4">
|
|
141
|
+
<StatCard label={mon.rss || 'RSS'} value={formatBytes(system.memory.rss)} />
|
|
142
|
+
<StatCard label={mon.uptime || 'Uptime'} value={formatUptime(system.uptimeMs)} />
|
|
143
|
+
<StatCard label={mon.nodeVersion || 'Node'} value={system.nodeVersion} />
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</section>
|
|
147
|
+
|
|
148
|
+
{/* Application */}
|
|
149
|
+
<section>
|
|
150
|
+
<h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground mb-3">
|
|
151
|
+
<Zap size={13} className="text-muted-foreground" />
|
|
152
|
+
{mon.application || 'Application'}
|
|
153
|
+
</h3>
|
|
154
|
+
<div className="grid grid-cols-3 gap-4">
|
|
155
|
+
<StatCard label={mon.requests || 'Requests'} value={application.agentRequests} />
|
|
156
|
+
<StatCard label={mon.toolCalls || 'Tool Calls'} value={application.toolExecutions} />
|
|
157
|
+
<StatCard label={mon.avgResponse || 'Avg Response'} value={application.avgResponseTimeMs > 0 ? `${application.avgResponseTimeMs}ms` : '—'} />
|
|
158
|
+
<StatCard
|
|
159
|
+
label={mon.tokens || 'Tokens'}
|
|
160
|
+
value={`${(application.totalTokens.input + application.totalTokens.output).toLocaleString()}`}
|
|
161
|
+
sub={`↑${application.totalTokens.input.toLocaleString()} ↓${application.totalTokens.output.toLocaleString()}`}
|
|
162
|
+
/>
|
|
163
|
+
<StatCard label={mon.errors || 'Errors'} value={application.errors} />
|
|
164
|
+
</div>
|
|
165
|
+
</section>
|
|
166
|
+
|
|
167
|
+
{/* Knowledge Base */}
|
|
168
|
+
<section>
|
|
169
|
+
<h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground mb-3">
|
|
170
|
+
<Database size={13} className="text-muted-foreground" />
|
|
171
|
+
{mon.knowledgeBase || 'Knowledge Base'}
|
|
172
|
+
</h3>
|
|
173
|
+
<div className="grid grid-cols-3 gap-4">
|
|
174
|
+
<StatCard label={mon.files || 'Files'} value={knowledgeBase.fileCount} />
|
|
175
|
+
<StatCard label={mon.totalSize || 'Total Size'} value={formatBytes(knowledgeBase.totalSizeBytes)} />
|
|
176
|
+
<StatCard label={mon.rootPath || 'Root'} value={knowledgeBase.root.split('/').pop() ?? knowledgeBase.root} sub={knowledgeBase.root} />
|
|
177
|
+
</div>
|
|
178
|
+
</section>
|
|
179
|
+
|
|
180
|
+
{/* MCP */}
|
|
181
|
+
<section>
|
|
182
|
+
<h3 className="flex items-center gap-1.5 text-xs font-semibold text-foreground mb-3">
|
|
183
|
+
<HardDrive size={13} className="text-muted-foreground" />
|
|
184
|
+
MCP
|
|
185
|
+
</h3>
|
|
186
|
+
<div className="grid grid-cols-3 gap-4">
|
|
187
|
+
<StatCard
|
|
188
|
+
label={mon.mcpStatus || 'Status'}
|
|
189
|
+
value={mcp.running ? (mon.mcpRunning || 'Running') : (mon.mcpStopped || 'Stopped')}
|
|
190
|
+
/>
|
|
191
|
+
<StatCard label={mon.mcpPort || 'Port'} value={mcp.port} />
|
|
192
|
+
</div>
|
|
193
|
+
</section>
|
|
194
|
+
|
|
195
|
+
{/* Refresh indicator */}
|
|
196
|
+
<div className="flex items-center gap-1.5 text-[10px] text-muted-foreground">
|
|
197
|
+
<RefreshCw size={10} className={loading ? 'animate-spin' : ''} />
|
|
198
|
+
{mon.autoRefresh || 'Auto-refresh every 5s'}
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
@@ -2,12 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { Puzzle } from 'lucide-react';
|
|
4
4
|
import { getAllRenderers, setRendererEnabled } from '@/lib/renderers/registry';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
pluginStates: Record<string, boolean>;
|
|
8
|
-
setPluginStates: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
|
|
9
|
-
t: any;
|
|
10
|
-
}
|
|
5
|
+
import { Toggle } from './Primitives';
|
|
6
|
+
import type { PluginsTabProps } from './types';
|
|
11
7
|
|
|
12
8
|
export function PluginsTab({ pluginStates, setPluginStates, t }: PluginsTabProps) {
|
|
13
9
|
return (
|
|
@@ -58,28 +54,9 @@ export function PluginsTab({ pluginStates, setPluginStates, t }: PluginsTabProps
|
|
|
58
54
|
</div>
|
|
59
55
|
|
|
60
56
|
{isCore ? (
|
|
61
|
-
<
|
|
62
|
-
className="shrink-0 w-9 h-5 rounded-full bg-amber-600 relative cursor-not-allowed opacity-60"
|
|
63
|
-
title={t.settings.plugins.coreHint ?? 'Core renderer — always enabled'}
|
|
64
|
-
>
|
|
65
|
-
<span className="absolute top-[3px] left-[18px] w-3.5 h-3.5 rounded-full shadow-sm bg-white" />
|
|
66
|
-
</span>
|
|
57
|
+
<Toggle checked={true} disabled />
|
|
67
58
|
) : (
|
|
68
|
-
<
|
|
69
|
-
onClick={() => {
|
|
70
|
-
const next = !enabled;
|
|
71
|
-
setRendererEnabled(renderer.id, next);
|
|
72
|
-
setPluginStates(s => ({ ...s, [renderer.id]: next }));
|
|
73
|
-
}}
|
|
74
|
-
role="switch"
|
|
75
|
-
aria-checked={enabled}
|
|
76
|
-
className={`shrink-0 w-9 h-5 rounded-full transition-colors relative ${enabled ? 'bg-amber-600' : 'bg-muted border border-border'}`}
|
|
77
|
-
title={enabled ? t.settings.plugins.enabled : t.settings.plugins.disabled}
|
|
78
|
-
>
|
|
79
|
-
<span
|
|
80
|
-
className={`absolute top-[3px] w-3.5 h-3.5 rounded-full shadow-sm transition-all ${enabled ? 'left-[18px] bg-white' : 'left-[3px] bg-muted-foreground/50'}`}
|
|
81
|
-
/>
|
|
82
|
-
</button>
|
|
59
|
+
<Toggle checked={enabled} onChange={(next) => { setRendererEnabled(renderer.id, next); setPluginStates(s => ({ ...s, [renderer.id]: next })); }} title={enabled ? t.settings.plugins.enabled : t.settings.plugins.disabled} />
|
|
83
60
|
)}
|
|
84
61
|
</div>
|
|
85
62
|
</div>
|
|
@@ -39,6 +39,50 @@ export function EnvBadge({ overridden }: { overridden: boolean }) {
|
|
|
39
39
|
);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* 🟢 MINOR #6: Toggle component with aria accessibility
|
|
44
|
+
* @param {boolean} checked - Toggle state
|
|
45
|
+
* @param {function} onChange - Called when toggle state changes (if no onClick provided)
|
|
46
|
+
* @param {string} size - 'sm' (h-4 w-7) or 'md' (h-5 w-9)
|
|
47
|
+
* @param {boolean} disabled - Disable toggle
|
|
48
|
+
* @param {string} title - Tooltip text
|
|
49
|
+
* @param {function} onClick - Custom click handler (takes priority over onChange). Call onChange directly if needed.
|
|
50
|
+
*
|
|
51
|
+
* Usage:
|
|
52
|
+
* - Basic: `<Toggle checked={x} onChange={setX} />`
|
|
53
|
+
* - With custom handler: `<Toggle checked={x} onClick={(e) => { e.stopPropagation(); await save(); }} />`
|
|
54
|
+
* - In lists: Use `onClick` to prevent event bubbling; manually call `onChange` for state sync
|
|
55
|
+
*/
|
|
56
|
+
export function Toggle({ checked, onChange, size = 'md', disabled, title, onClick }: {
|
|
57
|
+
checked: boolean;
|
|
58
|
+
onChange?: (checked: boolean) => void;
|
|
59
|
+
size?: 'sm' | 'md';
|
|
60
|
+
disabled?: boolean;
|
|
61
|
+
title?: string;
|
|
62
|
+
onClick?: (e: React.MouseEvent) => void;
|
|
63
|
+
}) {
|
|
64
|
+
const sm = size === 'sm';
|
|
65
|
+
return (
|
|
66
|
+
<button
|
|
67
|
+
type="button"
|
|
68
|
+
role="switch"
|
|
69
|
+
aria-checked={checked}
|
|
70
|
+
disabled={disabled}
|
|
71
|
+
title={title}
|
|
72
|
+
onClick={onClick ?? (() => onChange?.(!checked))}
|
|
73
|
+
className={`relative inline-flex shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-60 ${
|
|
74
|
+
sm ? 'h-4 w-7' : 'h-5 w-9'
|
|
75
|
+
} ${checked ? 'bg-amber-500' : 'bg-muted'}`}
|
|
76
|
+
>
|
|
77
|
+
<span
|
|
78
|
+
className={`pointer-events-none inline-block rounded-full bg-white shadow-sm transition-transform ${
|
|
79
|
+
sm ? 'h-3 w-3' : 'h-4 w-4'
|
|
80
|
+
} ${checked ? (sm ? 'translate-x-3' : 'translate-x-4') : 'translate-x-0'}`}
|
|
81
|
+
/>
|
|
82
|
+
</button>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
42
86
|
export function ApiKeyInput({ value, onChange, placeholder, disabled }: {
|
|
43
87
|
value: string;
|
|
44
88
|
onChange: (v: string) => void;
|
|
@@ -58,3 +102,28 @@ export function ApiKeyInput({ value, onChange, placeholder, disabled }: {
|
|
|
58
102
|
/>
|
|
59
103
|
);
|
|
60
104
|
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 💡 SUGGESTION #10: Unified primary button primitive for amber actions
|
|
108
|
+
* Replaces inline `style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}` pattern
|
|
109
|
+
*/
|
|
110
|
+
export function PrimaryButton({ children, disabled, onClick, type = 'button', className = '', ...props }: {
|
|
111
|
+
children: React.ReactNode;
|
|
112
|
+
disabled?: boolean;
|
|
113
|
+
onClick?: () => void;
|
|
114
|
+
type?: 'button' | 'submit';
|
|
115
|
+
className?: string;
|
|
116
|
+
} & React.ButtonHTMLAttributes<HTMLButtonElement>) {
|
|
117
|
+
return (
|
|
118
|
+
<button
|
|
119
|
+
type={type}
|
|
120
|
+
onClick={onClick}
|
|
121
|
+
disabled={disabled}
|
|
122
|
+
className={`px-4 py-2 text-sm font-medium rounded-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed ${className}`}
|
|
123
|
+
style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}
|
|
124
|
+
{...props}
|
|
125
|
+
>
|
|
126
|
+
{children}
|
|
127
|
+
</button>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
t: any;
|
|
5
|
-
}
|
|
3
|
+
import type { ShortcutsTabProps } from './types';
|
|
6
4
|
|
|
7
5
|
export function ShortcutsTab({ t }: ShortcutsTabProps) {
|
|
8
6
|
return (
|
|
9
7
|
<div className="space-y-1">
|
|
10
|
-
{t.shortcuts.map((s: { description: string; keys: string[] }, i: number) => (
|
|
8
|
+
{t.shortcuts.map((s: { readonly description: string; readonly keys: readonly string[] }, i: number) => (
|
|
11
9
|
<div key={i} className="flex items-center justify-between py-2.5 border-b border-border last:border-0">
|
|
12
10
|
<span className="text-sm text-foreground">{s.description}</span>
|
|
13
11
|
<div className="flex items-center gap-1">
|
|
@@ -2,26 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback } from 'react';
|
|
4
4
|
import { RefreshCw, AlertCircle, CheckCircle2, Loader2, GitBranch, ExternalLink, Eye, EyeOff } from 'lucide-react';
|
|
5
|
-
import { SectionLabel } from './Primitives';
|
|
5
|
+
import { SectionLabel, PrimaryButton } from './Primitives';
|
|
6
6
|
import { apiFetch } from '@/lib/api';
|
|
7
|
+
import type { SyncStatus, SyncTabProps } from './types';
|
|
8
|
+
import type { Messages } from '@/lib/i18n';
|
|
7
9
|
|
|
8
|
-
export
|
|
9
|
-
enabled: boolean;
|
|
10
|
-
provider?: string;
|
|
11
|
-
remote?: string;
|
|
12
|
-
branch?: string;
|
|
13
|
-
lastSync?: string | null;
|
|
14
|
-
lastPull?: string | null;
|
|
15
|
-
unpushed?: string;
|
|
16
|
-
conflicts?: Array<{ file: string; time: string }>;
|
|
17
|
-
lastError?: string | null;
|
|
18
|
-
autoCommitInterval?: number;
|
|
19
|
-
autoPullInterval?: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface SyncTabProps {
|
|
23
|
-
t: any;
|
|
24
|
-
}
|
|
10
|
+
export { SyncStatus }; // Re-export for backward compatibility
|
|
25
11
|
|
|
26
12
|
export function timeAgo(iso: string | null | undefined): string {
|
|
27
13
|
if (!iso) return 'never';
|
|
@@ -40,7 +26,7 @@ function isValidGitUrl(url: string): 'https' | 'ssh' | false {
|
|
|
40
26
|
return false;
|
|
41
27
|
}
|
|
42
28
|
|
|
43
|
-
function SyncEmptyState({ t, onInitComplete }: { t:
|
|
29
|
+
function SyncEmptyState({ t, onInitComplete }: { t: Messages; onInitComplete: () => void }) {
|
|
44
30
|
const syncT = t.settings?.sync;
|
|
45
31
|
|
|
46
32
|
const [remoteUrl, setRemoteUrl] = useState('');
|
|
@@ -169,18 +155,16 @@ function SyncEmptyState({ t, onInitComplete }: { t: any; onInitComplete: () => v
|
|
|
169
155
|
</div>
|
|
170
156
|
|
|
171
157
|
{/* Connect button */}
|
|
172
|
-
<
|
|
173
|
-
type="button"
|
|
158
|
+
<PrimaryButton
|
|
174
159
|
onClick={handleConnect}
|
|
175
160
|
disabled={!isValid || connecting}
|
|
176
|
-
className="flex items-center gap-2
|
|
177
|
-
style={{ background: 'var(--amber)', color: 'var(--amber-foreground)' }}
|
|
161
|
+
className="flex items-center gap-2"
|
|
178
162
|
>
|
|
179
163
|
{connecting && <Loader2 size={14} className="animate-spin" />}
|
|
180
164
|
{connecting
|
|
181
165
|
? (syncT?.connecting ?? 'Connecting...')
|
|
182
166
|
: (syncT?.connectButton ?? 'Connect & Start Sync')}
|
|
183
|
-
</
|
|
167
|
+
</PrimaryButton>
|
|
184
168
|
|
|
185
169
|
{/* Error */}
|
|
186
170
|
{error && (
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { Locale } from '@/lib/i18n';
|
|
1
|
+
import type { Locale, Messages } from '@/lib/i18n';
|
|
2
|
+
import type React from 'react';
|
|
2
3
|
|
|
3
4
|
export interface ProviderConfig {
|
|
4
5
|
apiKey: string;
|
|
@@ -32,7 +33,7 @@ export interface SettingsData {
|
|
|
32
33
|
envValues?: Record<string, string>;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
export type Tab = 'ai' | 'appearance' | 'knowledge' | 'mcp' | 'plugins' | '
|
|
36
|
+
export type Tab = 'ai' | 'appearance' | 'knowledge' | 'mcp' | 'plugins' | 'sync' | 'monitoring' | 'agents';
|
|
36
37
|
|
|
37
38
|
export const CONTENT_WIDTHS = [
|
|
38
39
|
{ value: '680px', label: 'Narrow (680px)' },
|
|
@@ -47,3 +48,116 @@ export const FONTS = [
|
|
|
47
48
|
{ value: 'geist', label: 'Geist', style: { fontFamily: 'var(--font-geist-sans), sans-serif' } },
|
|
48
49
|
{ value: 'ibm-plex-mono', label: 'IBM Plex Mono (mono)', style: { fontFamily: "'IBM Plex Mono', monospace" } },
|
|
49
50
|
];
|
|
51
|
+
|
|
52
|
+
/* ── MCP Types ────────────────────────────────────────────────── */
|
|
53
|
+
|
|
54
|
+
export interface McpStatus {
|
|
55
|
+
running: boolean;
|
|
56
|
+
transport: string;
|
|
57
|
+
endpoint: string;
|
|
58
|
+
port: number;
|
|
59
|
+
toolCount: number;
|
|
60
|
+
authConfigured: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface AgentInfo {
|
|
64
|
+
key: string;
|
|
65
|
+
name: string;
|
|
66
|
+
present: boolean;
|
|
67
|
+
installed: boolean;
|
|
68
|
+
scope?: string;
|
|
69
|
+
transport?: string;
|
|
70
|
+
configPath?: string;
|
|
71
|
+
hasProjectScope: boolean;
|
|
72
|
+
hasGlobalScope: boolean;
|
|
73
|
+
preferredTransport: 'stdio' | 'http';
|
|
74
|
+
// Snippet generation fields
|
|
75
|
+
format: 'json' | 'toml';
|
|
76
|
+
configKey: string;
|
|
77
|
+
globalNestedKey?: string;
|
|
78
|
+
globalPath: string;
|
|
79
|
+
projectPath?: string | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface SkillInfo {
|
|
83
|
+
name: string;
|
|
84
|
+
description: string;
|
|
85
|
+
path: string;
|
|
86
|
+
source: 'builtin' | 'user';
|
|
87
|
+
enabled: boolean;
|
|
88
|
+
editable: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** 🟢 MINOR #7: Moved from SyncTab.tsx for consistency */
|
|
92
|
+
export interface SyncStatus {
|
|
93
|
+
enabled: boolean;
|
|
94
|
+
provider?: string;
|
|
95
|
+
remote?: string;
|
|
96
|
+
branch?: string;
|
|
97
|
+
lastSync?: string | null;
|
|
98
|
+
lastPull?: string | null;
|
|
99
|
+
unpushed?: string;
|
|
100
|
+
conflicts?: Array<{ file: string; time: string }>;
|
|
101
|
+
lastError?: string | null;
|
|
102
|
+
autoCommitInterval?: number;
|
|
103
|
+
autoPullInterval?: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface McpTabProps {
|
|
107
|
+
t: Messages;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface AppearanceTabProps {
|
|
111
|
+
font: string;
|
|
112
|
+
setFont: (v: string) => void;
|
|
113
|
+
contentWidth: string;
|
|
114
|
+
setContentWidth: (v: string) => void;
|
|
115
|
+
dark: boolean;
|
|
116
|
+
setDark: (v: boolean) => void;
|
|
117
|
+
locale: Locale;
|
|
118
|
+
setLocale: (v: Locale) => void;
|
|
119
|
+
t: Messages;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface AiTabProps {
|
|
123
|
+
data: SettingsData;
|
|
124
|
+
updateAi: (patch: Partial<AiSettings>) => void;
|
|
125
|
+
updateAgent: (patch: Partial<AgentSettings>) => void;
|
|
126
|
+
t: Messages;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface KnowledgeTabProps {
|
|
130
|
+
data: SettingsData;
|
|
131
|
+
setData: React.Dispatch<React.SetStateAction<SettingsData | null>>;
|
|
132
|
+
t: Messages;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface PluginsTabProps {
|
|
136
|
+
pluginStates: Record<string, boolean>;
|
|
137
|
+
setPluginStates: React.Dispatch<React.SetStateAction<Record<string, boolean>>>;
|
|
138
|
+
t: Messages;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface SyncTabProps {
|
|
142
|
+
t: Messages;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface McpServerStatusProps {
|
|
146
|
+
status: McpStatus | null;
|
|
147
|
+
agents: AgentInfo[];
|
|
148
|
+
t: Messages;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface McpAgentInstallProps {
|
|
152
|
+
agents: AgentInfo[];
|
|
153
|
+
t: Messages;
|
|
154
|
+
onRefresh: () => void;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface McpSkillsSectionProps {
|
|
158
|
+
t: Messages;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface ShortcutsTabProps {
|
|
162
|
+
t: Messages;
|
|
163
|
+
}
|
package/app/instrumentation.ts
CHANGED
|
@@ -7,9 +7,14 @@ export async function register() {
|
|
|
7
7
|
const configPath = join(homedir(), '.mindos', 'config.json');
|
|
8
8
|
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
9
9
|
if (config.sync?.enabled && config.mindRoot) {
|
|
10
|
-
//
|
|
10
|
+
// Turbopack statically analyzes ALL forms of require/import — including
|
|
11
|
+
// createRequire() calls. The only way to load a runtime-computed path
|
|
12
|
+
// is to hide the require call inside a Function constructor, which is
|
|
13
|
+
// opaque to bundler static analysis.
|
|
11
14
|
const syncModule = resolve(process.cwd(), '..', 'bin', 'lib', 'sync.js');
|
|
12
|
-
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
16
|
+
const dynamicRequire = new Function('id', 'return require(id)') as (id: string) => any;
|
|
17
|
+
const { startSyncDaemon } = dynamicRequire(syncModule);
|
|
13
18
|
await startSyncDaemon(config.mindRoot);
|
|
14
19
|
}
|
|
15
20
|
} catch {
|