@geminilight/mindos 0.5.22 → 0.5.24
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 +7 -14
- package/app/app/api/bootstrap/route.ts +1 -0
- package/app/app/globals.css +14 -0
- package/app/app/setup/page.tsx +3 -2
- package/app/components/ActivityBar.tsx +183 -0
- package/app/components/AskFab.tsx +39 -97
- package/app/components/AskModal.tsx +13 -371
- package/app/components/Breadcrumb.tsx +4 -4
- package/app/components/FileTree.tsx +21 -4
- package/app/components/Logo.tsx +39 -0
- package/app/components/Panel.tsx +152 -0
- package/app/components/RightAskPanel.tsx +72 -0
- package/app/components/SettingsModal.tsx +9 -241
- package/app/components/SidebarLayout.tsx +426 -12
- package/app/components/SyncStatusBar.tsx +74 -53
- package/app/components/TableOfContents.tsx +4 -2
- package/app/components/ask/AskContent.tsx +418 -0
- package/app/components/ask/MessageList.tsx +2 -2
- package/app/components/panels/AgentsPanel.tsx +231 -0
- package/app/components/panels/PanelHeader.tsx +35 -0
- package/app/components/panels/PluginsPanel.tsx +106 -0
- package/app/components/panels/SearchPanel.tsx +178 -0
- package/app/components/panels/SyncPopover.tsx +105 -0
- package/app/components/renderers/csv/TableView.tsx +4 -4
- package/app/components/settings/AiTab.tsx +39 -1
- package/app/components/settings/KnowledgeTab.tsx +116 -2
- package/app/components/settings/McpTab.tsx +6 -6
- package/app/components/settings/SettingsContent.tsx +343 -0
- package/app/components/settings/types.ts +1 -1
- package/app/components/setup/index.tsx +2 -23
- package/app/hooks/useResizeDrag.ts +78 -0
- package/app/lib/agent/index.ts +0 -1
- package/app/lib/agent/model.ts +33 -10
- package/app/lib/format.ts +19 -0
- package/app/lib/i18n-en.ts +6 -6
- package/app/lib/i18n-zh.ts +5 -5
- package/app/next-env.d.ts +1 -1
- package/app/next.config.ts +1 -1
- package/bin/cli.js +27 -97
- package/package.json +4 -2
- package/scripts/setup.js +2 -12
- package/skills/mindos/SKILL.md +226 -8
- package/skills/mindos-zh/SKILL.md +226 -8
- package/app/lib/agent/skill-rules.ts +0 -70
- package/app/package-lock.json +0 -15736
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import AskContent from '@/components/ask/AskContent';
|
|
4
|
+
import { useResizeDrag } from '@/hooks/useResizeDrag';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_WIDTH = 380;
|
|
7
|
+
const MIN_WIDTH = 300;
|
|
8
|
+
const MAX_WIDTH_ABS = 700;
|
|
9
|
+
const MAX_WIDTH_RATIO = 0.45;
|
|
10
|
+
|
|
11
|
+
interface RightAskPanelProps {
|
|
12
|
+
open: boolean;
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
currentFile?: string;
|
|
15
|
+
initialMessage?: string;
|
|
16
|
+
onFirstMessage?: () => void;
|
|
17
|
+
width: number;
|
|
18
|
+
onWidthChange: (w: number) => void;
|
|
19
|
+
onWidthCommit: (w: number) => void;
|
|
20
|
+
askMode?: 'panel' | 'popup';
|
|
21
|
+
onModeSwitch?: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function RightAskPanel({
|
|
25
|
+
open, onClose, currentFile, initialMessage, onFirstMessage,
|
|
26
|
+
width, onWidthChange, onWidthCommit, askMode, onModeSwitch,
|
|
27
|
+
}: RightAskPanelProps) {
|
|
28
|
+
const handleMouseDown = useResizeDrag({
|
|
29
|
+
width,
|
|
30
|
+
minWidth: MIN_WIDTH,
|
|
31
|
+
maxWidth: MAX_WIDTH_ABS,
|
|
32
|
+
maxWidthRatio: MAX_WIDTH_RATIO,
|
|
33
|
+
direction: 'left',
|
|
34
|
+
onResize: onWidthChange,
|
|
35
|
+
onResizeEnd: onWidthCommit,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<aside
|
|
40
|
+
className={`
|
|
41
|
+
hidden md:flex fixed top-0 right-0 h-screen z-30
|
|
42
|
+
flex-col bg-card border-l border-border
|
|
43
|
+
transition-transform duration-200 ease-out
|
|
44
|
+
${open ? 'translate-x-0' : 'translate-x-full pointer-events-none'}
|
|
45
|
+
`}
|
|
46
|
+
style={{ width: `${width}px` }}
|
|
47
|
+
role="complementary"
|
|
48
|
+
aria-label="MindOS Agent panel"
|
|
49
|
+
>
|
|
50
|
+
<AskContent
|
|
51
|
+
visible={open}
|
|
52
|
+
variant="panel"
|
|
53
|
+
currentFile={open ? currentFile : undefined}
|
|
54
|
+
initialMessage={initialMessage}
|
|
55
|
+
onFirstMessage={onFirstMessage}
|
|
56
|
+
onClose={onClose}
|
|
57
|
+
askMode={askMode}
|
|
58
|
+
onModeSwitch={onModeSwitch}
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
{/* Drag resize handle — LEFT edge */}
|
|
62
|
+
<div
|
|
63
|
+
className="absolute top-0 -left-[3px] w-[6px] h-full cursor-col-resize z-40 group hidden md:block"
|
|
64
|
+
onMouseDown={handleMouseDown}
|
|
65
|
+
>
|
|
66
|
+
<div className="absolute left-[2px] top-0 w-[2px] h-full opacity-0 group-hover:opacity-100 bg-amber-500/60 transition-opacity" />
|
|
67
|
+
</div>
|
|
68
|
+
</aside>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { DEFAULT_WIDTH as RIGHT_ASK_DEFAULT_WIDTH, MIN_WIDTH as RIGHT_ASK_MIN_WIDTH, MAX_WIDTH_ABS as RIGHT_ASK_MAX_WIDTH };
|
|
@@ -1,21 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { useLocale } from '@/lib/LocaleContext';
|
|
6
|
-
import { getAllRenderers, loadDisabledState, isRendererEnabled } from '@/lib/renderers/registry';
|
|
7
|
-
import { apiFetch } from '@/lib/api';
|
|
8
|
-
import '@/lib/renderers/index';
|
|
9
|
-
import type { AiSettings, AgentSettings, SettingsData, Tab } from './settings/types';
|
|
10
|
-
import { FONTS } from './settings/types';
|
|
11
|
-
import { AiTab } from './settings/AiTab';
|
|
12
|
-
import { AppearanceTab } from './settings/AppearanceTab';
|
|
13
|
-
import { KnowledgeTab } from './settings/KnowledgeTab';
|
|
14
|
-
import { PluginsTab } from './settings/PluginsTab';
|
|
15
|
-
import { SyncTab } from './settings/SyncTab';
|
|
16
|
-
import { McpTab } from './settings/McpTab';
|
|
17
|
-
import { MonitoringTab } from './settings/MonitoringTab';
|
|
18
|
-
import { AgentsTab } from './settings/AgentsTab';
|
|
3
|
+
import SettingsContent from './settings/SettingsContent';
|
|
4
|
+
import type { Tab } from './settings/types';
|
|
19
5
|
|
|
20
6
|
interface SettingsModalProps {
|
|
21
7
|
open: boolean;
|
|
@@ -24,238 +10,20 @@ interface SettingsModalProps {
|
|
|
24
10
|
}
|
|
25
11
|
|
|
26
12
|
export default function SettingsModal({ open, onClose, initialTab }: SettingsModalProps) {
|
|
27
|
-
const [tab, setTab] = useState<Tab>('ai');
|
|
28
|
-
const [data, setData] = useState<SettingsData | null>(null);
|
|
29
|
-
const [saving, setSaving] = useState(false);
|
|
30
|
-
const [status, setStatus] = useState<'idle' | 'saved' | 'error' | 'load-error'>('idle');
|
|
31
|
-
const { t, locale, setLocale } = useLocale();
|
|
32
|
-
const saveTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
33
|
-
const dataLoaded = useRef(false);
|
|
34
|
-
|
|
35
|
-
// Appearance state (localStorage-based)
|
|
36
|
-
const [font, setFont] = useState('lora');
|
|
37
|
-
const [contentWidth, setContentWidth] = useState('780px');
|
|
38
|
-
const [dark, setDark] = useState(true);
|
|
39
|
-
// Plugin enabled state
|
|
40
|
-
const [pluginStates, setPluginStates] = useState<Record<string, boolean>>({});
|
|
41
|
-
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
if (!open) { dataLoaded.current = false; return; }
|
|
44
|
-
apiFetch<SettingsData>('/api/settings').then(d => { setData(d); dataLoaded.current = true; }).catch(() => setStatus('load-error'));
|
|
45
|
-
setFont(localStorage.getItem('prose-font') ?? 'lora');
|
|
46
|
-
setContentWidth(localStorage.getItem('content-width') ?? '780px');
|
|
47
|
-
const stored = localStorage.getItem('theme');
|
|
48
|
-
setDark(stored ? stored === 'dark' : window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
49
|
-
loadDisabledState();
|
|
50
|
-
const initial: Record<string, boolean> = {};
|
|
51
|
-
for (const r of getAllRenderers()) initial[r.id] = isRendererEnabled(r.id);
|
|
52
|
-
setPluginStates(initial);
|
|
53
|
-
setStatus('idle');
|
|
54
|
-
}, [open]);
|
|
55
|
-
|
|
56
|
-
// Switch to requested tab when opening with initialTab
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
if (open && initialTab) setTab(initialTab);
|
|
59
|
-
}, [open, initialTab]);
|
|
60
|
-
|
|
61
|
-
// Apply font immediately
|
|
62
|
-
useEffect(() => {
|
|
63
|
-
const fontMap: Record<string, string> = {
|
|
64
|
-
'lora': "'Lora', Georgia, serif",
|
|
65
|
-
'ibm-plex-sans': "'IBM Plex Sans', sans-serif",
|
|
66
|
-
'geist': 'var(--font-geist-sans), sans-serif',
|
|
67
|
-
'ibm-plex-mono': "'IBM Plex Mono', monospace",
|
|
68
|
-
};
|
|
69
|
-
document.documentElement.style.setProperty('--prose-font-override', fontMap[font] ?? '');
|
|
70
|
-
localStorage.setItem('prose-font', font);
|
|
71
|
-
}, [font]);
|
|
72
|
-
|
|
73
|
-
// Apply content width immediately
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
document.documentElement.style.setProperty('--content-width-override', contentWidth);
|
|
76
|
-
localStorage.setItem('content-width', contentWidth);
|
|
77
|
-
}, [contentWidth]);
|
|
78
|
-
|
|
79
|
-
// Esc to close
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
if (!open) return;
|
|
82
|
-
const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
|
|
83
|
-
window.addEventListener('keydown', handler);
|
|
84
|
-
return () => window.removeEventListener('keydown', handler);
|
|
85
|
-
}, [open, onClose]);
|
|
86
|
-
|
|
87
|
-
// Auto-save with debounce when data changes
|
|
88
|
-
const doSave = useCallback(async (d: SettingsData) => {
|
|
89
|
-
setSaving(true);
|
|
90
|
-
try {
|
|
91
|
-
await apiFetch('/api/settings', {
|
|
92
|
-
method: 'POST',
|
|
93
|
-
headers: { 'Content-Type': 'application/json' },
|
|
94
|
-
body: JSON.stringify({ ai: d.ai, agent: d.agent, mindRoot: d.mindRoot, webPassword: d.webPassword, authToken: d.authToken }),
|
|
95
|
-
});
|
|
96
|
-
setStatus('saved');
|
|
97
|
-
setTimeout(() => setStatus('idle'), 2500);
|
|
98
|
-
} catch {
|
|
99
|
-
setStatus('error');
|
|
100
|
-
setTimeout(() => setStatus('idle'), 2500);
|
|
101
|
-
} finally {
|
|
102
|
-
setSaving(false);
|
|
103
|
-
}
|
|
104
|
-
}, []);
|
|
105
|
-
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
if (!data || !dataLoaded.current) return;
|
|
108
|
-
clearTimeout(saveTimer.current);
|
|
109
|
-
saveTimer.current = setTimeout(() => doSave(data), 800);
|
|
110
|
-
return () => clearTimeout(saveTimer.current);
|
|
111
|
-
}, [data, doSave]);
|
|
112
|
-
|
|
113
|
-
const updateAi = useCallback((patch: Partial<AiSettings>) => {
|
|
114
|
-
setData(d => d ? { ...d, ai: { ...d.ai, ...patch } } : d);
|
|
115
|
-
}, []);
|
|
116
|
-
|
|
117
|
-
const updateAgent = useCallback((patch: Partial<AgentSettings>) => {
|
|
118
|
-
setData(d => d ? { ...d, agent: { ...(d.agent ?? {}), ...patch } } : d);
|
|
119
|
-
}, []);
|
|
120
|
-
|
|
121
|
-
const restoreFromEnv = useCallback(async () => {
|
|
122
|
-
if (!data) return;
|
|
123
|
-
const defaults: AiSettings = {
|
|
124
|
-
provider: 'anthropic',
|
|
125
|
-
providers: {
|
|
126
|
-
anthropic: { apiKey: '', model: '' },
|
|
127
|
-
openai: { apiKey: '', model: '', baseUrl: '' },
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
// Set defaults — auto-save will persist them
|
|
131
|
-
setData(d => d ? { ...d, ai: defaults } : d);
|
|
132
|
-
// 🟢 MINOR #4: Refetch after auto-save completes (800ms debounce + 500ms save operation)
|
|
133
|
-
// Rather than magic 1200ms, wait for save to finish before refetching env-resolved values
|
|
134
|
-
const DEBOUNCE_DELAY = 800;
|
|
135
|
-
const SAVE_OPERATION_TIME = 500;
|
|
136
|
-
setTimeout(() => {
|
|
137
|
-
apiFetch<SettingsData>('/api/settings').then(d => { setData(d); }).catch(() => setStatus('error'));
|
|
138
|
-
}, DEBOUNCE_DELAY + SAVE_OPERATION_TIME);
|
|
139
|
-
}, [data]);
|
|
140
|
-
|
|
141
13
|
if (!open) return null;
|
|
142
14
|
|
|
143
|
-
const env = data?.envOverrides ?? {};
|
|
144
|
-
|
|
145
|
-
const TABS: { id: Tab; label: string; icon: React.ReactNode }[] = [
|
|
146
|
-
{ id: 'ai', label: t.settings.tabs.ai, icon: <Sparkles size={13} /> },
|
|
147
|
-
{ id: 'appearance', label: t.settings.tabs.appearance, icon: <Palette size={13} /> },
|
|
148
|
-
{ id: 'knowledge', label: t.settings.tabs.knowledge, icon: <Database size={13} /> },
|
|
149
|
-
{ id: 'sync', label: t.settings.tabs.sync ?? 'Sync', icon: <RefreshCw size={13} /> },
|
|
150
|
-
{ id: 'mcp', label: t.settings.tabs.mcp ?? 'MCP', icon: <Plug size={13} /> },
|
|
151
|
-
{ id: 'plugins', label: t.settings.tabs.plugins, icon: <Puzzle size={13} /> },
|
|
152
|
-
{ id: 'monitoring', label: t.settings.tabs.monitoring, icon: <Activity size={13} /> },
|
|
153
|
-
{ id: 'agents', label: t.settings.tabs.agents ?? 'Agents', icon: <Users size={13} /> },
|
|
154
|
-
];
|
|
155
|
-
|
|
156
15
|
return (
|
|
157
16
|
<div
|
|
158
17
|
className="fixed inset-0 z-50 flex items-end md:items-start justify-center md:pt-[10vh] modal-backdrop"
|
|
159
18
|
onClick={(e) => e.target === e.currentTarget && onClose()}
|
|
160
19
|
>
|
|
161
|
-
<div role="dialog" aria-modal="true" aria-label="Settings" className="w-full md:max-w-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
<div className="flex items-center justify-between px-4 py-3 border-b border-border shrink-0">
|
|
169
|
-
<div className="flex items-center gap-2 text-sm font-medium text-foreground">
|
|
170
|
-
<Settings size={15} className="text-muted-foreground" />
|
|
171
|
-
<span className="font-display">{t.settings.title}</span>
|
|
172
|
-
</div>
|
|
173
|
-
<button onClick={onClose} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors">
|
|
174
|
-
<X size={15} />
|
|
175
|
-
</button>
|
|
176
|
-
</div>
|
|
177
|
-
|
|
178
|
-
{/* Tabs */}
|
|
179
|
-
<div className="flex border-b border-border px-4 shrink-0 overflow-x-auto scrollbar-none">
|
|
180
|
-
{TABS.map(t => (
|
|
181
|
-
<button
|
|
182
|
-
key={t.id}
|
|
183
|
-
onClick={() => setTab(t.id)}
|
|
184
|
-
className={`flex items-center gap-1.5 px-3 py-2.5 text-xs font-medium transition-colors border-b-2 -mb-px whitespace-nowrap ${
|
|
185
|
-
tab === t.id
|
|
186
|
-
? 'border-amber-500 text-foreground'
|
|
187
|
-
: 'border-transparent text-muted-foreground hover:text-foreground'
|
|
188
|
-
}`}
|
|
189
|
-
>
|
|
190
|
-
{t.icon}
|
|
191
|
-
{t.label}
|
|
192
|
-
</button>
|
|
193
|
-
))}
|
|
194
|
-
</div>
|
|
195
|
-
|
|
196
|
-
{/* Content */}
|
|
197
|
-
<div className="flex-1 overflow-y-auto min-h-0 px-5 py-5 space-y-5">
|
|
198
|
-
{status === 'load-error' && (tab === 'ai' || tab === 'knowledge') ? (
|
|
199
|
-
<div className="flex flex-col items-center gap-2 py-8 text-center">
|
|
200
|
-
<AlertCircle size={20} className="text-destructive" />
|
|
201
|
-
<p className="text-sm text-destructive font-medium">Failed to load settings</p>
|
|
202
|
-
<p className="text-xs text-muted-foreground">Check that the server is running and AUTH_TOKEN is configured correctly.</p>
|
|
203
|
-
</div>
|
|
204
|
-
) : !data && tab !== 'appearance' && tab !== 'mcp' && tab !== 'sync' && tab !== 'agents' ? (
|
|
205
|
-
<div className="flex justify-center py-8">
|
|
206
|
-
<Loader2 size={18} className="animate-spin text-muted-foreground" />
|
|
207
|
-
</div>
|
|
208
|
-
) : (
|
|
209
|
-
<>
|
|
210
|
-
{tab === 'ai' && data?.ai && <AiTab data={data} updateAi={updateAi} updateAgent={updateAgent} t={t} />}
|
|
211
|
-
{tab === 'appearance' && <AppearanceTab font={font} setFont={setFont} contentWidth={contentWidth} setContentWidth={setContentWidth} dark={dark} setDark={setDark} locale={locale} setLocale={setLocale} t={t} />}
|
|
212
|
-
{tab === 'knowledge' && data && <KnowledgeTab data={data} setData={setData} t={t} />}
|
|
213
|
-
{tab === 'plugins' && <PluginsTab pluginStates={pluginStates} setPluginStates={setPluginStates} t={t} />}
|
|
214
|
-
{tab === 'sync' && <SyncTab t={t} />}
|
|
215
|
-
{tab === 'mcp' && <McpTab t={t} />}
|
|
216
|
-
{tab === 'monitoring' && <MonitoringTab t={t} />}
|
|
217
|
-
{tab === 'agents' && <AgentsTab t={t} />}
|
|
218
|
-
</>
|
|
219
|
-
)}
|
|
220
|
-
</div>
|
|
221
|
-
|
|
222
|
-
{/* Footer — status bar + contextual actions */}
|
|
223
|
-
{(tab === 'ai' || tab === 'knowledge') && (
|
|
224
|
-
<div className="px-5 py-2.5 border-t border-border shrink-0 flex items-center justify-between">
|
|
225
|
-
<div className="flex items-center gap-3">
|
|
226
|
-
{tab === 'ai' && Object.values(env).some(Boolean) && (
|
|
227
|
-
<button
|
|
228
|
-
onClick={restoreFromEnv}
|
|
229
|
-
disabled={saving || !data}
|
|
230
|
-
className="flex items-center gap-1.5 px-3 py-1 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
|
|
231
|
-
>
|
|
232
|
-
<RotateCcw size={12} />
|
|
233
|
-
{t.settings.ai.restoreFromEnv}
|
|
234
|
-
</button>
|
|
235
|
-
)}
|
|
236
|
-
{tab === 'knowledge' && (
|
|
237
|
-
<a
|
|
238
|
-
href="/setup?force=1"
|
|
239
|
-
className="flex items-center gap-1.5 px-3 py-1 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
|
|
240
|
-
>
|
|
241
|
-
<RotateCcw size={12} />
|
|
242
|
-
{t.settings.reconfigure}
|
|
243
|
-
</a>
|
|
244
|
-
)}
|
|
245
|
-
</div>
|
|
246
|
-
<div className="flex items-center gap-1.5 text-xs" role="status" aria-live="polite">
|
|
247
|
-
{saving && (
|
|
248
|
-
<><Loader2 size={12} className="animate-spin text-muted-foreground" /><span className="text-muted-foreground">{t.settings.save}...</span></>
|
|
249
|
-
)}
|
|
250
|
-
{status === 'saved' && (
|
|
251
|
-
<><CheckCircle2 size={12} className="text-success" /><span className="text-success">{t.settings.saved}</span></>
|
|
252
|
-
)}
|
|
253
|
-
{status === 'error' && (
|
|
254
|
-
<><AlertCircle size={12} className="text-destructive" /><span className="text-destructive">{t.settings.saveFailed}</span></>
|
|
255
|
-
)}
|
|
256
|
-
</div>
|
|
257
|
-
</div>
|
|
258
|
-
)}
|
|
20
|
+
<div role="dialog" aria-modal="true" aria-label="Settings" className="w-full md:max-w-3xl md:mx-4 bg-card border-t md:border border-border rounded-t-2xl md:rounded-xl shadow-2xl flex flex-col h-[88vh] md:h-[80vh] md:max-h-[85vh]">
|
|
21
|
+
<SettingsContent
|
|
22
|
+
visible={open}
|
|
23
|
+
variant="modal"
|
|
24
|
+
onClose={onClose}
|
|
25
|
+
initialTab={initialTab}
|
|
26
|
+
/>
|
|
259
27
|
</div>
|
|
260
28
|
</div>
|
|
261
29
|
);
|