@geminilight/mindos 0.5.35 → 0.5.37

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.
@@ -75,7 +75,8 @@ body {
75
75
  --ring: var(--amber);
76
76
  --radius: 0.5rem;
77
77
  --amber: #c8873a;
78
- --amber-dim: rgba(200, 135, 58, 0.12);
78
+ --amber-dim: rgba(200, 135, 58, 0.18);
79
+ --amber-subtle: rgba(200, 135, 30, 0.08);
79
80
  --amber-foreground: #131210;
80
81
  --success: #7aad80;
81
82
  --error: #c85050;
@@ -109,7 +110,8 @@ body {
109
110
  --input: rgba(232, 228, 220, 0.1);
110
111
  --ring: var(--amber);
111
112
  --amber: #d4954a;
112
- --amber-dim: rgba(212, 149, 74, 0.12);
113
+ --amber-dim: rgba(212, 149, 74, 0.20);
114
+ --amber-subtle: rgba(212, 149, 74, 0.10);
113
115
  --amber-foreground: #131210;
114
116
  --success: #7aad80;
115
117
  --error: #c85050;
@@ -298,6 +300,10 @@ body {
298
300
  backdrop-filter: blur(8px);
299
301
  -webkit-backdrop-filter: blur(8px);
300
302
  }
303
+ .dark .modal-backdrop {
304
+ background: rgba(0, 0, 0, 0.65);
305
+ }
306
+ }
301
307
 
302
308
  /* Micro type scale: text-2xs = 10px (between nothing and text-xs 12px) */
303
309
  @layer utilities {
@@ -15,10 +15,6 @@ const DIR_ICONS: Record<string, string> = {
15
15
 
16
16
  const EMPTY_FILES = ['INSTRUCTION.md', 'README.md', 'CONFIG.json'];
17
17
 
18
- /* Shared amber-subtle background — used as inline style because Tailwind can't handle
19
- CSS var() with fallback in arbitrary values: bg-[var(--amber-subtle,rgba(...))] breaks. */
20
- const amberSubtleBg = 'var(--amber-subtle, rgba(200,135,30,0.08))';
21
-
22
18
  interface GuideCardProps {
23
19
  /** Called when user clicks a file/dir to open it in FileView */
24
20
  onNavigate?: (path: string) => void;
@@ -155,8 +151,7 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
155
151
  // After all next-steps done → final state (auto-dismisses after 8s)
156
152
  if (allDone && allNextDone) {
157
153
  return (
158
- <div className="mb-6 rounded-xl border border-[var(--amber)] px-5 py-4 flex items-center gap-3 animate-in fade-in duration-300"
159
- style={{ background: amberSubtleBg }}>
154
+ <div className="mb-6 rounded-xl border border-[var(--amber)] px-5 py-4 flex items-center gap-3 animate-in fade-in duration-300 bg-[var(--amber-subtle)]">
160
155
  <Sparkles size={16} className="animate-spin-slow text-[var(--amber)]" />
161
156
  <span className="text-sm font-semibold flex-1 text-foreground">
162
157
  ✨ {g.done.titleFinal}
@@ -178,8 +173,7 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
178
173
  if (allDone) {
179
174
  const step = nextSteps[nextIdx];
180
175
  return (
181
- <div className="mb-6 rounded-xl border border-[var(--amber)] px-5 py-4 animate-in fade-in duration-300"
182
- style={{ background: amberSubtleBg }}>
176
+ <div className="mb-6 rounded-xl border border-[var(--amber)] px-5 py-4 animate-in fade-in duration-300 bg-[var(--amber-subtle)]">
183
177
  <div className="flex items-center gap-3">
184
178
  <Sparkles size={16} className="text-[var(--amber)]" />
185
179
  <span className="text-sm font-semibold flex-1 text-foreground">
@@ -204,8 +198,7 @@ export default function GuideCard({ onNavigate }: GuideCardProps) {
204
198
 
205
199
  // Main guide card with 3 tasks
206
200
  return (
207
- <div className="mb-6 rounded-xl border border-[var(--amber)] overflow-hidden"
208
- style={{ background: amberSubtleBg }}>
201
+ <div className="mb-6 rounded-xl border border-[var(--amber)] overflow-hidden bg-[var(--amber-subtle)]">
209
202
 
210
203
  {/* Header */}
211
204
  <div className="flex items-center gap-3 px-5 pt-4 pb-2">
@@ -81,7 +81,7 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
81
81
  className="flex-1 flex items-center gap-3 px-4 py-3 rounded-xl border border-border bg-card transition-all duration-150 hover:border-amber-500/50 hover:bg-amber-500/8"
82
82
  >
83
83
  <Sparkles size={15} className="shrink-0 text-[var(--amber)]" />
84
- <span className="text-sm flex-1 text-left text-foreground" aria-live="polite" aria-atomic="true">
84
+ <span className="text-sm flex-1 text-left text-foreground">
85
85
  {suggestions[suggestionIdx]}
86
86
  </span>
87
87
  <kbd className="hidden sm:inline-flex items-center gap-0.5 px-2 py-0.5 rounded text-xs font-mono font-medium bg-[var(--amber-dim)] text-[var(--amber)]">
@@ -215,8 +215,7 @@ export default function HomeContent({ recent, existingFiles }: { recent: RecentF
215
215
  >
216
216
  <ChevronDown
217
217
  size={12}
218
- className="transition-transform duration-200"
219
- style={{ transform: showAll ? 'rotate(180deg)' : undefined }}
218
+ className={`transition-transform duration-200 ${showAll ? 'rotate-180' : ''}`}
220
219
  />
221
220
  <span>{showAll ? t.home.showLess : t.home.showMore}</span>
222
221
  </button>
@@ -80,7 +80,7 @@ export default function OnboardingView() {
80
80
  className="max-w-2xl mx-auto mb-6 flex items-center gap-2.5 px-4 py-3 rounded-lg border border-destructive/30 bg-destructive/5 text-sm text-destructive"
81
81
  >
82
82
  <AlertCircle size={16} className="shrink-0" />
83
- <span className="flex-1">{ob.initError ?? 'Initialization failed. Please try again.'} ({error})</span>
83
+ <span className="flex-1">{ob.initError ?? 'Initialization failed. Please try again.'}</span>
84
84
  <button
85
85
  onClick={() => setError(null)}
86
86
  className="text-xs underline shrink-0 hover:opacity-80"
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
 
3
+ import { AlertCircle } from 'lucide-react';
3
4
  import AskContent from '@/components/ask/AskContent';
5
+ import ErrorBoundary from '@/components/ErrorBoundary';
4
6
  import { useResizeDrag } from '@/hooks/useResizeDrag';
5
7
 
6
8
  const DEFAULT_WIDTH = 380;
@@ -47,16 +49,29 @@ export default function RightAskPanel({
47
49
  role="complementary"
48
50
  aria-label="MindOS Agent panel"
49
51
  >
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
- />
52
+ <ErrorBoundary fallback={
53
+ <div className="flex flex-col items-center justify-center h-full gap-3 px-6 text-center">
54
+ <AlertCircle size={20} className="text-muted-foreground" />
55
+ <p className="text-sm text-muted-foreground">AI panel encountered an error.</p>
56
+ <button
57
+ onClick={() => window.location.reload()}
58
+ className="text-xs px-3 py-1.5 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
59
+ >
60
+ Reload page
61
+ </button>
62
+ </div>
63
+ }>
64
+ <AskContent
65
+ visible={open}
66
+ variant="panel"
67
+ currentFile={open ? currentFile : undefined}
68
+ initialMessage={initialMessage}
69
+ onFirstMessage={onFirstMessage}
70
+ onClose={onClose}
71
+ askMode={askMode}
72
+ onModeSwitch={onModeSwitch}
73
+ />
74
+ </ErrorBoundary>
60
75
 
61
76
  {/* Drag resize handle — LEFT edge */}
62
77
  <div
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useState, useEffect, useCallback } from 'react';
3
+ import { useState, useEffect, useCallback, useRef } from 'react';
4
4
  import { useRouter } from 'next/navigation';
5
5
  import { getAllRenderers, isRendererEnabled, setRendererEnabled, loadDisabledState } from '@/lib/renderers/registry';
6
6
  import { Toggle } from '../settings/Primitives';
@@ -27,25 +27,25 @@ export default function PluginsPanel({ active, maximized, onMaximize }: PluginsP
27
27
  setMounted(true);
28
28
  }, []);
29
29
 
30
- // Check which entry files exist (once on mount + when active)
30
+ // Check which entry files exist — fetch once on mount, cache result
31
+ const fetchedRef = useRef(false);
31
32
  useEffect(() => {
32
- if (!mounted || !active) return;
33
+ if (!mounted || fetchedRef.current) return;
34
+ fetchedRef.current = true;
33
35
  const entryPaths = getAllRenderers()
34
36
  .map(r => r.entryPath)
35
37
  .filter((p): p is string => !!p);
36
38
  if (entryPaths.length === 0) return;
37
39
 
38
- // Check each file via HEAD-like GET lightweight
39
- Promise.all(
40
- entryPaths.map(path =>
41
- fetch(`/api/file?path=${encodeURIComponent(path)}`, { method: 'GET' })
42
- .then(r => r.ok ? path : null)
43
- .catch(() => null)
44
- )
45
- ).then(results => {
46
- setExistingFiles(new Set(results.filter((p): p is string => p !== null)));
47
- });
48
- }, [mounted, active]);
40
+ // Single request: fetch all file paths and check which entry paths exist
41
+ fetch('/api/files')
42
+ .then(r => r.ok ? r.json() : [])
43
+ .then((allPaths: string[]) => {
44
+ const pathSet = new Set(allPaths);
45
+ setExistingFiles(new Set(entryPaths.filter(p => pathSet.has(p))));
46
+ })
47
+ .catch(() => {});
48
+ }, [mounted]);
49
49
 
50
50
  const renderers = mounted ? getAllRenderers() : [];
51
51
  const enabledCount = mounted ? renderers.filter(r => isRendererEnabled(r.id)).length : 0;
@@ -86,7 +86,9 @@ export default function PluginsPanel({ active, maximized, onMaximize }: PluginsP
86
86
  ${!enabled ? 'opacity-50' : ''}
87
87
  `}
88
88
  onClick={canOpen ? () => handleOpen(r.entryPath!) : undefined}
89
+ onKeyDown={canOpen ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleOpen(r.entryPath!); } } : undefined}
89
90
  role={canOpen ? 'link' : undefined}
91
+ tabIndex={canOpen ? 0 : undefined}
90
92
  >
91
93
  {/* Top row: status dot + icon + name + toggle */}
92
94
  <div className="flex items-center justify-between gap-2">
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useCallback, useSyncExternalStore, useRef } from 'react';
4
- import { Copy, Check, RefreshCw, Trash2, Sparkles, ChevronDown, ChevronRight, Loader2, Cpu, Zap, Database as DatabaseIcon, HardDrive } from 'lucide-react';
4
+ import { Copy, Check, RefreshCw, Trash2, Sparkles, ChevronDown, ChevronRight, Loader2, Cpu, Zap, Database as DatabaseIcon, HardDrive, RotateCcw } from 'lucide-react';
5
5
  import type { KnowledgeTabProps } from './types';
6
6
  import { Field, Input, EnvBadge, SectionLabel, Toggle } from './Primitives';
7
7
  import { apiFetch } from '@/lib/api';
@@ -49,6 +49,27 @@ export function KnowledgeTab({ data, setData, t }: KnowledgeTabProps) {
49
49
  });
50
50
  }, [guideDismissed]);
51
51
 
52
+ const handleRestartWalkthrough = useCallback(() => {
53
+ apiFetch('/api/setup', {
54
+ method: 'PATCH',
55
+ headers: { 'Content-Type': 'application/json' },
56
+ body: JSON.stringify({
57
+ guideState: {
58
+ active: true,
59
+ dismissed: false,
60
+ walkthroughStep: 0,
61
+ walkthroughDismissed: false,
62
+ },
63
+ }),
64
+ })
65
+ .then(() => {
66
+ setGuideActive(true);
67
+ setGuideDismissed(false);
68
+ window.dispatchEvent(new Event('guide-state-updated'));
69
+ })
70
+ .catch(err => console.error('Failed to restart walkthrough:', err));
71
+ }, []);
72
+
52
73
  const origin = useSyncExternalStore(
53
74
  () => () => {},
54
75
  () => `${window.location.protocol}//${window.location.hostname}`,
@@ -207,6 +228,13 @@ export function KnowledgeTab({ data, setData, t }: KnowledgeTabProps) {
207
228
  </div>
208
229
  <Toggle checked={!guideDismissed} onChange={() => handleGuideToggle()} />
209
230
  </div>
231
+ <button
232
+ onClick={handleRestartWalkthrough}
233
+ className="flex items-center gap-1.5 mt-2 px-3 py-1.5 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
234
+ >
235
+ <RotateCcw size={12} />
236
+ {k.restartWalkthrough ?? 'Restart walkthrough'}
237
+ </button>
210
238
  </div>
211
239
  )}
212
240
 
@@ -234,6 +234,7 @@ export const en = {
234
234
  authTokenClear: 'Clear token',
235
235
  authTokenResetConfirm: 'Regenerate token? All existing MCP clients will need to update their config.',
236
236
  authTokenMcpPort: 'MCP port',
237
+ restartWalkthrough: 'Restart walkthrough',
237
238
  },
238
239
  sync: {
239
240
  emptyTitle: 'Cross-device Sync',
@@ -259,6 +259,7 @@ export const zh = {
259
259
  authTokenClear: '清除令牌',
260
260
  authTokenResetConfirm: '重新生成令牌?所有 MCP 客户端配置都需要更新。',
261
261
  authTokenMcpPort: 'MCP 端口',
262
+ restartWalkthrough: '重新开始引导',
262
263
  },
263
264
  sync: {
264
265
  emptyTitle: '跨设备同步',
package/app/next-env.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="next" />
2
2
  /// <reference types="next/image-types/global" />
3
- import "./.next/dev/types/routes.d.ts";
3
+ import "./.next/types/routes.d.ts";
4
4
 
5
5
  // NOTE: This file should not be edited
6
6
  // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminilight/mindos",
3
- "version": "0.5.35",
3
+ "version": "0.5.37",
4
4
  "description": "MindOS — Human-Agent Collaborative Mind System. Local-first knowledge base that syncs your mind to all AI Agents via MCP.",
5
5
  "keywords": [
6
6
  "mindos",
@@ -1,63 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useEffect } from 'react';
4
- import { X, Sparkles } from 'lucide-react';
5
- import { useLocale } from '@/lib/LocaleContext';
6
-
7
- export default function WelcomeBanner() {
8
- const { t } = useLocale();
9
- const s = t.setup;
10
- const [visible, setVisible] = useState(false);
11
-
12
- useEffect(() => {
13
- // Show banner if ?welcome=1 is in the URL
14
- const params = new URLSearchParams(window.location.search);
15
- if (params.get('welcome') === '1') {
16
- setVisible(true);
17
- // Remove ?welcome=1 from URL without reloading
18
- const url = new URL(window.location.href);
19
- url.searchParams.delete('welcome');
20
- const newUrl = url.pathname + (url.searchParams.size > 0 ? '?' + url.searchParams.toString() : '');
21
- window.history.replaceState({}, '', newUrl);
22
- }
23
- }, []);
24
-
25
- if (!visible) return null;
26
-
27
- return (
28
- <div className="mb-6 rounded-xl border px-5 py-4 flex items-start gap-4"
29
- style={{ background: 'var(--amber-subtle, rgba(200,135,30,0.08))', borderColor: 'var(--amber)' }}>
30
- <Sparkles size={18} className="mt-0.5 shrink-0" style={{ color: 'var(--amber)' }} />
31
- <div className="flex-1 min-w-0">
32
- <p className="text-sm font-semibold mb-1" style={{ color: 'var(--foreground)' }}>
33
- {s.welcomeTitle}
34
- </p>
35
- <p className="text-xs leading-relaxed mb-3" style={{ color: 'var(--muted-foreground)' }}>
36
- {s.welcomeDesc}
37
- </p>
38
- <div className="flex flex-wrap gap-2">
39
- <a href="/setup?force=1" className="text-xs px-3 py-1.5 rounded-lg border transition-colors"
40
- style={{ borderColor: 'var(--amber)', color: 'var(--amber)' }}>
41
- {s.welcomeLinkReconfigure}
42
- </a>
43
- <button onClick={() => window.dispatchEvent(new KeyboardEvent('keydown', { key: '/', metaKey: true, bubbles: true }))}
44
- className="text-xs px-3 py-1.5 rounded-lg border transition-colors"
45
- style={{ borderColor: 'var(--border)', color: 'var(--muted-foreground)' }}>
46
- {s.welcomeLinkAskAI}
47
- </button>
48
- <button
49
- onClick={() => window.dispatchEvent(new KeyboardEvent('keydown', { key: ',', metaKey: true, bubbles: true }))}
50
- className="text-xs px-3 py-1.5 rounded-lg border transition-colors"
51
- style={{ borderColor: 'var(--border)', color: 'var(--muted-foreground)' }}>
52
- {s.welcomeLinkMCP}
53
- </button>
54
- </div>
55
- </div>
56
- <button onClick={() => setVisible(false)}
57
- className="p-1 rounded hover:bg-muted transition-colors shrink-0"
58
- style={{ color: 'var(--muted-foreground)' }}>
59
- <X size={14} />
60
- </button>
61
- </div>
62
- );
63
- }