@geminilight/mindos 0.6.35 → 0.6.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.
Files changed (139) hide show
  1. package/_standalone/.mindos-build-version +1 -1
  2. package/_standalone/.next/BUILD_ID +1 -1
  3. package/_standalone/.next/app-path-routes-manifest.json +19 -19
  4. package/_standalone/.next/build-manifest.json +2 -2
  5. package/_standalone/.next/cache/.previewinfo +1 -1
  6. package/_standalone/.next/cache/.rscinfo +1 -1
  7. package/_standalone/.next/cache/config.json +3 -3
  8. package/_standalone/.next/prerender-manifest.json +3 -3
  9. package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
  10. package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/_standalone/.next/server/app/_global-error.html +2 -2
  12. package/_standalone/.next/server/app/_global-error.rsc +1 -1
  13. package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/_standalone/.next/server/app/_not-found/page.js +1 -1
  20. package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  21. package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  22. package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
  23. package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
  24. package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
  25. package/_standalone/.next/server/app/agents/page.js +1 -1
  26. package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
  27. package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  28. package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
  29. package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
  30. package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
  31. package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
  32. package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
  33. package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
  34. package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
  35. package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
  36. package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
  37. package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
  38. package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
  39. package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
  40. package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
  41. package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
  42. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
  43. package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  44. package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
  45. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  46. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  47. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  48. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  49. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  50. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  51. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  52. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  53. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  54. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  55. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  56. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  57. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  58. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  59. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  60. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  61. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  62. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  63. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  64. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  65. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  66. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  67. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  68. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  69. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  71. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  73. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  74. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  75. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  76. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  77. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  78. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  79. package/_standalone/.next/server/app/changes/page.js +1 -1
  80. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  81. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/echo/[segment]/page.js +1 -1
  83. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  84. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  85. package/_standalone/.next/server/app/echo/page.js +1 -1
  86. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  87. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  88. package/_standalone/.next/server/app/explore/page.js +1 -1
  89. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  90. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  91. package/_standalone/.next/server/app/help/page.js +1 -1
  92. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  93. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  94. package/_standalone/.next/server/app/login/page.js +1 -1
  95. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  96. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  97. package/_standalone/.next/server/app/page.js +1 -1
  98. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  99. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  100. package/_standalone/.next/server/app/setup/page.js +2 -2
  101. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  102. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  103. package/_standalone/.next/server/app/trash/page.js +2 -2
  104. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  105. package/_standalone/.next/server/app/view/[...path]/page.js +3 -3
  106. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  107. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  108. package/_standalone/.next/server/app-paths-manifest.json +19 -19
  109. package/_standalone/.next/server/chunks/4931.js +11 -11
  110. package/_standalone/.next/server/chunks/{733.js → 912.js} +2 -2
  111. package/_standalone/.next/server/next-font-manifest.js +1 -1
  112. package/_standalone/.next/server/next-font-manifest.json +1 -1
  113. package/_standalone/.next/server/pages/500.html +2 -2
  114. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  115. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  116. package/_standalone/.next/static/chunks/{1263-31c1efe3fd8f3f99.js → 1263-79beb8734dee7bbd.js} +2 -2
  117. package/_standalone/.next/static/chunks/app/{layout-8f28cae88387ff74.js → layout-00844243aa76e1b6.js} +46 -46
  118. package/_standalone/.next/static/chunks/app/{page-c1d8353c59791595.js → page-99cc295c70b85882.js} +2 -2
  119. package/_standalone/.next/static/chunks/app/setup/page-01ab1f549d636057.js +1 -0
  120. package/_standalone/.next/static/chunks/app/trash/{page-82c45f29e38465b8.js → page-83afad27eeceb4b3.js} +1 -1
  121. package/_standalone/.next/static/chunks/app/view/[...path]/{page-8d1f8b9247021c34.js → page-725df5864296687f.js} +2 -2
  122. package/_standalone/.next/trace +53 -53
  123. package/_standalone/components/settings/UpdateTab.tsx +16 -0
  124. package/_standalone/components/setup/StepAI.tsx +66 -3
  125. package/_standalone/components/setup/StepDots.tsx +18 -10
  126. package/_standalone/components/setup/StepKB.tsx +26 -0
  127. package/_standalone/components/setup/constants.tsx +4 -3
  128. package/_standalone/lib/i18n/modules/onboarding.ts +14 -6
  129. package/app/components/settings/UpdateTab.tsx +16 -0
  130. package/app/components/setup/StepAI.tsx +66 -3
  131. package/app/components/setup/StepDots.tsx +18 -10
  132. package/app/components/setup/StepKB.tsx +26 -0
  133. package/app/components/setup/constants.tsx +4 -3
  134. package/app/components/setup/index.tsx +22 -32
  135. package/app/lib/i18n/modules/onboarding.ts +14 -6
  136. package/package.json +1 -1
  137. package/_standalone/.next/static/chunks/app/setup/page-f8a85accc3be554f.js +0 -1
  138. /package/_standalone/.next/static/{o0geoEkWzQqvkqAs4aTu5 → krusEkNclII1OrOrikHPD}/_buildManifest.js +0 -0
  139. /package/_standalone/.next/static/{o0geoEkWzQqvkqAs4aTu5 → krusEkNclII1OrOrikHPD}/_ssgManifest.js +0 -0
@@ -8,8 +8,10 @@ import { useLocale } from '@/lib/LocaleContext';
8
8
  interface MindosDesktopBridge {
9
9
  checkUpdate: () => Promise<{ available: boolean; version?: string }>;
10
10
  installUpdate: () => Promise<void>;
11
+ onUpdateAvailable?: (cb: (info: { version?: string }) => void) => () => void;
11
12
  onUpdateProgress?: (cb: (progress: { percent: number }) => void) => () => void;
12
13
  onUpdateReady?: (cb: () => void) => () => void;
14
+ onUpdateError?: (cb: (info: { message?: string }) => void) => () => void;
13
15
  getAppInfo?: () => Promise<{ version?: string }>;
14
16
  }
15
17
 
@@ -83,12 +85,26 @@ function DesktopUpdateTab() {
83
85
  }).catch((err) => { console.warn("[UpdateTab] getAppInfo failed:", err); });
84
86
  handleCheck();
85
87
  const cleanups: Array<() => void> = [];
88
+ if (bridge.onUpdateAvailable) {
89
+ cleanups.push(bridge.onUpdateAvailable((info) => {
90
+ setAvailable(true);
91
+ if (info?.version) setVersion(info.version);
92
+ // Move to idle so the "Update to vX.Y.Z" button appears
93
+ setState((prev) => prev === 'checking' ? 'idle' : prev);
94
+ }));
95
+ }
86
96
  if (bridge.onUpdateProgress) {
87
97
  cleanups.push(bridge.onUpdateProgress((p) => setProgress(Math.round(p.percent))));
88
98
  }
89
99
  if (bridge.onUpdateReady) {
90
100
  cleanups.push(bridge.onUpdateReady(() => setState('ready')));
91
101
  }
102
+ if (bridge.onUpdateError) {
103
+ cleanups.push(bridge.onUpdateError((info) => {
104
+ setState('error');
105
+ setErrorMsg(info?.message || 'Update failed. Please try again.');
106
+ }));
107
+ }
92
108
  return () => cleanups.forEach((fn) => fn());
93
109
  // eslint-disable-next-line react-hooks/exhaustive-deps
94
110
  }, []);
@@ -1,16 +1,35 @@
1
1
  'use client';
2
2
 
3
- import { Brain, Zap, SkipForward, CheckCircle2 } from 'lucide-react';
3
+ import { useState, useEffect } from 'react';
4
+ import { Brain, Zap, SkipForward, CheckCircle2, ChevronDown, ChevronRight, Copy } from 'lucide-react';
4
5
  import { Field, Input, ApiKeyInput } from '@/components/settings/Primitives';
5
- import type { SetupState, SetupMessages } from './types';
6
+ import type { SetupState, SetupMessages, PortStatus } from './types';
7
+ import StepPorts from './StepPorts';
6
8
 
7
9
  export interface StepAIProps {
8
10
  state: SetupState;
9
11
  update: <K extends keyof SetupState>(key: K, val: SetupState[K]) => void;
10
12
  s: SetupMessages;
13
+ onCopyToken: () => void;
14
+ // Port props (embedded in Advanced section)
15
+ webPortStatus: PortStatus;
16
+ mcpPortStatus: PortStatus;
17
+ setWebPortStatus: (s: PortStatus) => void;
18
+ setMcpPortStatus: (s: PortStatus) => void;
19
+ checkPort: (port: number, which: 'web' | 'mcp') => void;
20
+ portConflict: boolean;
11
21
  }
12
22
 
13
- export default function StepAI({ state, update, s }: StepAIProps) {
23
+ export default function StepAI({ state, update, s, onCopyToken, webPortStatus, mcpPortStatus, setWebPortStatus, setMcpPortStatus, checkPort, portConflict }: StepAIProps) {
24
+ const [portsOpen, setPortsOpen] = useState(false);
25
+
26
+ // Auto-expand Advanced section if port check finds a problem
27
+ useEffect(() => {
28
+ if (!portsOpen && (webPortStatus.available === false || mcpPortStatus.available === false || portConflict)) {
29
+ setPortsOpen(true);
30
+ }
31
+ }, [webPortStatus.available, mcpPortStatus.available, portConflict, portsOpen]);
32
+
14
33
  const providers = [
15
34
  { id: 'anthropic' as const, icon: <Brain size={18} />, label: 'Anthropic', desc: 'Claude — claude-sonnet-4-6' },
16
35
  { id: 'openai' as const, icon: <Zap size={18} />, label: 'OpenAI', desc: 'GPT or any OpenAI-compatible API' },
@@ -70,6 +89,50 @@ export default function StepAI({ state, update, s }: StepAIProps) {
70
89
  )}
71
90
  </div>
72
91
  )}
92
+
93
+ {/* Advanced: Port Settings (collapsed) */}
94
+ <div className="pt-3 mt-1" style={{ borderTop: '1px solid var(--border)' }}>
95
+ <button
96
+ type="button"
97
+ onClick={() => setPortsOpen(!portsOpen)}
98
+ className="flex items-center gap-1.5 text-xs font-medium"
99
+ style={{ color: 'var(--muted-foreground)' }}>
100
+ {portsOpen ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
101
+ {s.advancedPorts}
102
+ </button>
103
+ {portsOpen && (
104
+ <div className="mt-3 space-y-5">
105
+ {/* MCP Auth Token (read-only) */}
106
+ <div className="space-y-1.5">
107
+ <p className="text-xs font-medium" style={{ color: 'var(--foreground)' }}>
108
+ 🔑 {s.tokenSectionTitle}
109
+ </p>
110
+ <div className="flex items-center gap-2">
111
+ <code className="flex-1 truncate text-xs font-mono px-3 py-2 rounded-lg"
112
+ style={{ background: 'var(--muted)', color: 'var(--foreground)' }}>
113
+ {state.authToken}
114
+ </code>
115
+ <button type="button" onClick={onCopyToken}
116
+ className="flex items-center gap-1 px-2.5 py-2 text-xs rounded-lg border transition-colors hover:bg-muted shrink-0"
117
+ style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}>
118
+ <Copy size={12} /> {s.copyToken}
119
+ </button>
120
+ </div>
121
+ <p className="text-xs" style={{ color: 'var(--muted-foreground)' }}>
122
+ {s.tokenSectionHint}
123
+ </p>
124
+ </div>
125
+
126
+ {/* Port Settings */}
127
+ <StepPorts
128
+ state={state} update={update}
129
+ webPortStatus={webPortStatus} mcpPortStatus={mcpPortStatus}
130
+ setWebPortStatus={setWebPortStatus} setMcpPortStatus={setMcpPortStatus}
131
+ checkPort={checkPort} portConflict={portConflict} s={s}
132
+ />
133
+ </div>
134
+ )}
135
+ </div>
73
136
  </div>
74
137
  );
75
138
  }
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { CheckCircle2 } from 'lucide-react';
3
4
  import { useLocale } from '@/lib/LocaleContext';
4
5
 
5
6
  export interface StepDotsProps {
@@ -7,32 +8,39 @@ export interface StepDotsProps {
7
8
  setStep: (s: number) => void;
8
9
  stepTitles: readonly string[];
9
10
  disabled?: boolean;
11
+ /** Number of "numbered" steps to show (Confirm step is not numbered) */
12
+ numberedSteps?: number;
10
13
  }
11
14
 
12
- export default function StepDots({ step, setStep, stepTitles, disabled }: StepDotsProps) {
15
+ export default function StepDots({ step, setStep, stepTitles, disabled, numberedSteps }: StepDotsProps) {
13
16
  const { t } = useLocale();
17
+ const count = numberedSteps ?? stepTitles.length;
18
+ // Only render dots for numbered steps (exclude Confirm)
19
+ const dotsToShow = stepTitles.slice(0, count);
20
+ const isConfirmStep = step >= count;
21
+
14
22
  return (
15
23
  <div className="flex items-center gap-2 mb-8" role="navigation" aria-label="Setup steps">
16
- {stepTitles.map((title: string, i: number) => (
24
+ {dotsToShow.map((title: string, i: number) => (
17
25
  <div key={i} className="flex items-center gap-2">
18
- {i > 0 && <div className="w-8 h-px" style={{ background: i <= step ? 'var(--amber)' : 'var(--border)' }} />}
26
+ {i > 0 && <div className="w-8 h-px" style={{ background: i <= step || isConfirmStep ? 'var(--amber)' : 'var(--border)' }} />}
19
27
  <button onClick={() => setStep(i)}
20
28
  aria-current={i === step ? 'step' : undefined}
21
29
  aria-label={title}
22
30
  className="flex flex-col items-center gap-1 p-1 -m-1 disabled:cursor-not-allowed disabled:opacity-60"
23
- disabled={disabled || i >= step}
24
- title={(disabled || i >= step) ? t.hints.cannotJumpForward : undefined}>
31
+ disabled={disabled || i > step}
32
+ title={(disabled || i > step) ? t.hints.cannotJumpForward : undefined}>
25
33
  <div
26
34
  className="w-6 h-6 rounded-full text-xs font-medium flex items-center justify-center transition-colors"
27
35
  style={{
28
- background: i <= step ? 'var(--amber)' : 'var(--muted)',
29
- color: i <= step ? 'var(--amber-foreground)' : 'var(--muted-foreground)',
30
- opacity: i <= step ? 1 : 0.5,
36
+ background: (i < step || isConfirmStep) ? 'var(--amber)' : i === step ? 'var(--amber)' : 'var(--muted)',
37
+ color: (i <= step || isConfirmStep) ? 'var(--amber-foreground)' : 'var(--muted-foreground)',
38
+ opacity: (i <= step || isConfirmStep) ? 1 : 0.5,
31
39
  }}>
32
- {i + 1}
40
+ {(i < step || isConfirmStep) ? <CheckCircle2 size={14} /> : i + 1}
33
41
  </div>
34
42
  <span className="text-[10px] leading-tight hidden sm:inline max-w-[4rem] text-center truncate"
35
- style={{ color: i === step ? 'var(--foreground)' : 'var(--muted-foreground)', opacity: i <= step ? 1 : 0.5 }}>
43
+ style={{ color: (i === step && !isConfirmStep) ? 'var(--foreground)' : 'var(--muted-foreground)', opacity: (i <= step || isConfirmStep) ? 1 : 0.5 }}>
36
44
  {title}
37
45
  </span>
38
46
  </button>
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useRef } from 'react';
4
+ import { AlertCircle } from 'lucide-react';
4
5
  import { Field } from '@/components/settings/Primitives';
5
6
  import type { Messages } from '@/lib/i18n';
6
7
  import type { SetupState } from './types';
@@ -26,6 +27,7 @@ export interface StepKBProps {
26
27
 
27
28
  export default function StepKB({ state, update, t, homeDir }: StepKBProps) {
28
29
  const s = t.setup;
30
+ const [passwordTouched, setPasswordTouched] = useState(false);
29
31
  // Build platform-aware placeholder, e.g. /Users/alice/MindOS/mind or C:\Users\alice\MindOS\mind
30
32
  // Windows homedir always contains \, e.g. C:\Users\Alice — safe to detect by separator
31
33
  const sep = homeDir.includes('\\') ? '\\' : '/';
@@ -232,6 +234,30 @@ export default function StepKB({ state, update, t, homeDir }: StepKBProps) {
232
234
  </div>
233
235
  </div>
234
236
  )}
237
+
238
+ {/* ── Security ── */}
239
+ <div className="pt-2 mt-2" style={{ borderTop: '1px solid var(--border)' }}>
240
+ <Field label={<>{s.webPassword} <span style={{ color: 'var(--error)' }}>*</span></>} hint={s.webPasswordHint}>
241
+ <input
242
+ type="password"
243
+ value={state.webPassword}
244
+ onChange={e => { update('webPassword', e.target.value); setPasswordTouched(true); }}
245
+ onBlur={() => setPasswordTouched(true)}
246
+ placeholder="••••••••"
247
+ className="w-full px-3 py-2 text-sm rounded-lg border outline-none transition-colors focus-visible:ring-1 focus-visible:ring-ring"
248
+ style={{
249
+ background: 'var(--input, var(--card))',
250
+ borderColor: passwordTouched && !state.webPassword.trim() ? 'var(--error)' : 'var(--border)',
251
+ color: 'var(--foreground)',
252
+ }}
253
+ />
254
+ {passwordTouched && !state.webPassword.trim() && (
255
+ <p className="text-xs flex items-center gap-1 mt-1" style={{ color: 'var(--error)' }}>
256
+ <AlertCircle size={11} /> {s.webPasswordRequired}
257
+ </p>
258
+ )}
259
+ </Field>
260
+ </div>
235
261
  </div>
236
262
  );
237
263
  }
@@ -7,7 +7,8 @@ export const TEMPLATES: Array<{ id: Template; icon: React.ReactNode; dirs: strin
7
7
  { id: 'empty', icon: <FileText size={18} />, dirs: ['README.md', 'CONFIG.json', 'INSTRUCTION.md'] },
8
8
  ];
9
9
 
10
- export const TOTAL_STEPS = 6;
10
+ export const TOTAL_STEPS = 4;
11
11
  export const STEP_KB = 0;
12
- export const STEP_PORTS = 2;
13
- export const STEP_AGENTS = 4;
12
+ export const STEP_AI = 1;
13
+ export const STEP_AGENTS = 2;
14
+ export const STEP_REVIEW = 3;
@@ -15,7 +15,7 @@ export const onboardingEn = {
15
15
  dismiss: 'Dismiss',
16
16
  },
17
17
  setup: {
18
- stepTitles: ['Knowledge Base', 'AI Provider', 'Ports', 'Security', 'Agent Tools', 'Review'],
18
+ stepTitles: ['Knowledge Base', 'AI Configuration', 'Agent Tools', 'Confirm'],
19
19
  // Step 1
20
20
  kbPath: 'Knowledge base path',
21
21
  kbPathHint: 'Absolute path to your notes directory.',
@@ -56,8 +56,12 @@ export const onboardingEn = {
56
56
  generateToken: 'Generate',
57
57
  copyToken: 'Copy',
58
58
  copiedToken: 'Copied!',
59
- webPassword: 'Web UI Password',
60
- webPasswordHint: 'Optional. Protect browser access with a password.',
59
+ webPassword: 'Web Password',
60
+ webPasswordHint: 'Protect your knowledge base from unauthorized browser access.',
61
+ webPasswordRequired: 'Password is required to protect your data.',
62
+ advancedPorts: 'Advanced Settings',
63
+ tokenSectionTitle: 'MCP Auth Token (auto-generated)',
64
+ tokenSectionHint: 'Used by AI agents to connect to your MindOS server.',
61
65
  // Step 5 — Agent Tools
62
66
  agentToolsTitle: 'Agent Tools',
63
67
  agentToolsHint: 'Select AI agents to configure with MindOS MCP. Agents marked "not installed" can be configured now — they will work once you install the app.',
@@ -210,7 +214,7 @@ export const onboardingZh = {
210
214
  dismiss: '关闭',
211
215
  },
212
216
  setup: {
213
- stepTitles: ['知识库', 'AI 服务商', '端口', '安全', 'Agent 工具', '确认'],
217
+ stepTitles: ['知识库', 'AI 配置', 'Agent 工具', '确认'],
214
218
  // Step 1
215
219
  kbPath: '知识库路径',
216
220
  kbPathHint: '笔记目录的绝对路径。',
@@ -251,8 +255,12 @@ export const onboardingZh = {
251
255
  generateToken: '生成',
252
256
  copyToken: '复制',
253
257
  copiedToken: '已复制!',
254
- webPassword: '网页访问密码',
255
- webPasswordHint: '可选。设置后浏览器访问需要登录。',
258
+ webPassword: '访问密码',
259
+ webPasswordHint: '保护你的知识库,防止未授权的浏览器访问。',
260
+ webPasswordRequired: '密码是必填项,用于保护你的数据。',
261
+ advancedPorts: '高级设置',
262
+ tokenSectionTitle: 'MCP 认证令牌(已自动生成)',
263
+ tokenSectionHint: 'AI Agent 连接你的 MindOS 服务器时使用。',
256
264
  // Step 5 — Agent Tools
257
265
  agentToolsTitle: 'Agent 工具',
258
266
  agentToolsHint: '选择要与 MindOS MCP 配置的 AI Agent。标注「未安装」的 agent 可以先行配置,安装应用后即可生效。',
@@ -8,8 +8,10 @@ import { useLocale } from '@/lib/LocaleContext';
8
8
  interface MindosDesktopBridge {
9
9
  checkUpdate: () => Promise<{ available: boolean; version?: string }>;
10
10
  installUpdate: () => Promise<void>;
11
+ onUpdateAvailable?: (cb: (info: { version?: string }) => void) => () => void;
11
12
  onUpdateProgress?: (cb: (progress: { percent: number }) => void) => () => void;
12
13
  onUpdateReady?: (cb: () => void) => () => void;
14
+ onUpdateError?: (cb: (info: { message?: string }) => void) => () => void;
13
15
  getAppInfo?: () => Promise<{ version?: string }>;
14
16
  }
15
17
 
@@ -83,12 +85,26 @@ function DesktopUpdateTab() {
83
85
  }).catch((err) => { console.warn("[UpdateTab] getAppInfo failed:", err); });
84
86
  handleCheck();
85
87
  const cleanups: Array<() => void> = [];
88
+ if (bridge.onUpdateAvailable) {
89
+ cleanups.push(bridge.onUpdateAvailable((info) => {
90
+ setAvailable(true);
91
+ if (info?.version) setVersion(info.version);
92
+ // Move to idle so the "Update to vX.Y.Z" button appears
93
+ setState((prev) => prev === 'checking' ? 'idle' : prev);
94
+ }));
95
+ }
86
96
  if (bridge.onUpdateProgress) {
87
97
  cleanups.push(bridge.onUpdateProgress((p) => setProgress(Math.round(p.percent))));
88
98
  }
89
99
  if (bridge.onUpdateReady) {
90
100
  cleanups.push(bridge.onUpdateReady(() => setState('ready')));
91
101
  }
102
+ if (bridge.onUpdateError) {
103
+ cleanups.push(bridge.onUpdateError((info) => {
104
+ setState('error');
105
+ setErrorMsg(info?.message || 'Update failed. Please try again.');
106
+ }));
107
+ }
92
108
  return () => cleanups.forEach((fn) => fn());
93
109
  // eslint-disable-next-line react-hooks/exhaustive-deps
94
110
  }, []);
@@ -1,16 +1,35 @@
1
1
  'use client';
2
2
 
3
- import { Brain, Zap, SkipForward, CheckCircle2 } from 'lucide-react';
3
+ import { useState, useEffect } from 'react';
4
+ import { Brain, Zap, SkipForward, CheckCircle2, ChevronDown, ChevronRight, Copy } from 'lucide-react';
4
5
  import { Field, Input, ApiKeyInput } from '@/components/settings/Primitives';
5
- import type { SetupState, SetupMessages } from './types';
6
+ import type { SetupState, SetupMessages, PortStatus } from './types';
7
+ import StepPorts from './StepPorts';
6
8
 
7
9
  export interface StepAIProps {
8
10
  state: SetupState;
9
11
  update: <K extends keyof SetupState>(key: K, val: SetupState[K]) => void;
10
12
  s: SetupMessages;
13
+ onCopyToken: () => void;
14
+ // Port props (embedded in Advanced section)
15
+ webPortStatus: PortStatus;
16
+ mcpPortStatus: PortStatus;
17
+ setWebPortStatus: (s: PortStatus) => void;
18
+ setMcpPortStatus: (s: PortStatus) => void;
19
+ checkPort: (port: number, which: 'web' | 'mcp') => void;
20
+ portConflict: boolean;
11
21
  }
12
22
 
13
- export default function StepAI({ state, update, s }: StepAIProps) {
23
+ export default function StepAI({ state, update, s, onCopyToken, webPortStatus, mcpPortStatus, setWebPortStatus, setMcpPortStatus, checkPort, portConflict }: StepAIProps) {
24
+ const [portsOpen, setPortsOpen] = useState(false);
25
+
26
+ // Auto-expand Advanced section if port check finds a problem
27
+ useEffect(() => {
28
+ if (!portsOpen && (webPortStatus.available === false || mcpPortStatus.available === false || portConflict)) {
29
+ setPortsOpen(true);
30
+ }
31
+ }, [webPortStatus.available, mcpPortStatus.available, portConflict, portsOpen]);
32
+
14
33
  const providers = [
15
34
  { id: 'anthropic' as const, icon: <Brain size={18} />, label: 'Anthropic', desc: 'Claude — claude-sonnet-4-6' },
16
35
  { id: 'openai' as const, icon: <Zap size={18} />, label: 'OpenAI', desc: 'GPT or any OpenAI-compatible API' },
@@ -70,6 +89,50 @@ export default function StepAI({ state, update, s }: StepAIProps) {
70
89
  )}
71
90
  </div>
72
91
  )}
92
+
93
+ {/* Advanced: Port Settings (collapsed) */}
94
+ <div className="pt-3 mt-1" style={{ borderTop: '1px solid var(--border)' }}>
95
+ <button
96
+ type="button"
97
+ onClick={() => setPortsOpen(!portsOpen)}
98
+ className="flex items-center gap-1.5 text-xs font-medium"
99
+ style={{ color: 'var(--muted-foreground)' }}>
100
+ {portsOpen ? <ChevronDown size={12} /> : <ChevronRight size={12} />}
101
+ {s.advancedPorts}
102
+ </button>
103
+ {portsOpen && (
104
+ <div className="mt-3 space-y-5">
105
+ {/* MCP Auth Token (read-only) */}
106
+ <div className="space-y-1.5">
107
+ <p className="text-xs font-medium" style={{ color: 'var(--foreground)' }}>
108
+ 🔑 {s.tokenSectionTitle}
109
+ </p>
110
+ <div className="flex items-center gap-2">
111
+ <code className="flex-1 truncate text-xs font-mono px-3 py-2 rounded-lg"
112
+ style={{ background: 'var(--muted)', color: 'var(--foreground)' }}>
113
+ {state.authToken}
114
+ </code>
115
+ <button type="button" onClick={onCopyToken}
116
+ className="flex items-center gap-1 px-2.5 py-2 text-xs rounded-lg border transition-colors hover:bg-muted shrink-0"
117
+ style={{ borderColor: 'var(--border)', color: 'var(--foreground)' }}>
118
+ <Copy size={12} /> {s.copyToken}
119
+ </button>
120
+ </div>
121
+ <p className="text-xs" style={{ color: 'var(--muted-foreground)' }}>
122
+ {s.tokenSectionHint}
123
+ </p>
124
+ </div>
125
+
126
+ {/* Port Settings */}
127
+ <StepPorts
128
+ state={state} update={update}
129
+ webPortStatus={webPortStatus} mcpPortStatus={mcpPortStatus}
130
+ setWebPortStatus={setWebPortStatus} setMcpPortStatus={setMcpPortStatus}
131
+ checkPort={checkPort} portConflict={portConflict} s={s}
132
+ />
133
+ </div>
134
+ )}
135
+ </div>
73
136
  </div>
74
137
  );
75
138
  }
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import { CheckCircle2 } from 'lucide-react';
3
4
  import { useLocale } from '@/lib/LocaleContext';
4
5
 
5
6
  export interface StepDotsProps {
@@ -7,32 +8,39 @@ export interface StepDotsProps {
7
8
  setStep: (s: number) => void;
8
9
  stepTitles: readonly string[];
9
10
  disabled?: boolean;
11
+ /** Number of "numbered" steps to show (Confirm step is not numbered) */
12
+ numberedSteps?: number;
10
13
  }
11
14
 
12
- export default function StepDots({ step, setStep, stepTitles, disabled }: StepDotsProps) {
15
+ export default function StepDots({ step, setStep, stepTitles, disabled, numberedSteps }: StepDotsProps) {
13
16
  const { t } = useLocale();
17
+ const count = numberedSteps ?? stepTitles.length;
18
+ // Only render dots for numbered steps (exclude Confirm)
19
+ const dotsToShow = stepTitles.slice(0, count);
20
+ const isConfirmStep = step >= count;
21
+
14
22
  return (
15
23
  <div className="flex items-center gap-2 mb-8" role="navigation" aria-label="Setup steps">
16
- {stepTitles.map((title: string, i: number) => (
24
+ {dotsToShow.map((title: string, i: number) => (
17
25
  <div key={i} className="flex items-center gap-2">
18
- {i > 0 && <div className="w-8 h-px" style={{ background: i <= step ? 'var(--amber)' : 'var(--border)' }} />}
26
+ {i > 0 && <div className="w-8 h-px" style={{ background: i <= step || isConfirmStep ? 'var(--amber)' : 'var(--border)' }} />}
19
27
  <button onClick={() => setStep(i)}
20
28
  aria-current={i === step ? 'step' : undefined}
21
29
  aria-label={title}
22
30
  className="flex flex-col items-center gap-1 p-1 -m-1 disabled:cursor-not-allowed disabled:opacity-60"
23
- disabled={disabled || i >= step}
24
- title={(disabled || i >= step) ? t.hints.cannotJumpForward : undefined}>
31
+ disabled={disabled || i > step}
32
+ title={(disabled || i > step) ? t.hints.cannotJumpForward : undefined}>
25
33
  <div
26
34
  className="w-6 h-6 rounded-full text-xs font-medium flex items-center justify-center transition-colors"
27
35
  style={{
28
- background: i <= step ? 'var(--amber)' : 'var(--muted)',
29
- color: i <= step ? 'var(--amber-foreground)' : 'var(--muted-foreground)',
30
- opacity: i <= step ? 1 : 0.5,
36
+ background: (i < step || isConfirmStep) ? 'var(--amber)' : i === step ? 'var(--amber)' : 'var(--muted)',
37
+ color: (i <= step || isConfirmStep) ? 'var(--amber-foreground)' : 'var(--muted-foreground)',
38
+ opacity: (i <= step || isConfirmStep) ? 1 : 0.5,
31
39
  }}>
32
- {i + 1}
40
+ {(i < step || isConfirmStep) ? <CheckCircle2 size={14} /> : i + 1}
33
41
  </div>
34
42
  <span className="text-[10px] leading-tight hidden sm:inline max-w-[4rem] text-center truncate"
35
- style={{ color: i === step ? 'var(--foreground)' : 'var(--muted-foreground)', opacity: i <= step ? 1 : 0.5 }}>
43
+ style={{ color: (i === step && !isConfirmStep) ? 'var(--foreground)' : 'var(--muted-foreground)', opacity: (i <= step || isConfirmStep) ? 1 : 0.5 }}>
36
44
  {title}
37
45
  </span>
38
46
  </button>
@@ -1,6 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useEffect, useRef } from 'react';
4
+ import { AlertCircle } from 'lucide-react';
4
5
  import { Field } from '@/components/settings/Primitives';
5
6
  import type { Messages } from '@/lib/i18n';
6
7
  import type { SetupState } from './types';
@@ -26,6 +27,7 @@ export interface StepKBProps {
26
27
 
27
28
  export default function StepKB({ state, update, t, homeDir }: StepKBProps) {
28
29
  const s = t.setup;
30
+ const [passwordTouched, setPasswordTouched] = useState(false);
29
31
  // Build platform-aware placeholder, e.g. /Users/alice/MindOS/mind or C:\Users\alice\MindOS\mind
30
32
  // Windows homedir always contains \, e.g. C:\Users\Alice — safe to detect by separator
31
33
  const sep = homeDir.includes('\\') ? '\\' : '/';
@@ -232,6 +234,30 @@ export default function StepKB({ state, update, t, homeDir }: StepKBProps) {
232
234
  </div>
233
235
  </div>
234
236
  )}
237
+
238
+ {/* ── Security ── */}
239
+ <div className="pt-2 mt-2" style={{ borderTop: '1px solid var(--border)' }}>
240
+ <Field label={<>{s.webPassword} <span style={{ color: 'var(--error)' }}>*</span></>} hint={s.webPasswordHint}>
241
+ <input
242
+ type="password"
243
+ value={state.webPassword}
244
+ onChange={e => { update('webPassword', e.target.value); setPasswordTouched(true); }}
245
+ onBlur={() => setPasswordTouched(true)}
246
+ placeholder="••••••••"
247
+ className="w-full px-3 py-2 text-sm rounded-lg border outline-none transition-colors focus-visible:ring-1 focus-visible:ring-ring"
248
+ style={{
249
+ background: 'var(--input, var(--card))',
250
+ borderColor: passwordTouched && !state.webPassword.trim() ? 'var(--error)' : 'var(--border)',
251
+ color: 'var(--foreground)',
252
+ }}
253
+ />
254
+ {passwordTouched && !state.webPassword.trim() && (
255
+ <p className="text-xs flex items-center gap-1 mt-1" style={{ color: 'var(--error)' }}>
256
+ <AlertCircle size={11} /> {s.webPasswordRequired}
257
+ </p>
258
+ )}
259
+ </Field>
260
+ </div>
235
261
  </div>
236
262
  );
237
263
  }
@@ -7,7 +7,8 @@ export const TEMPLATES: Array<{ id: Template; icon: React.ReactNode; dirs: strin
7
7
  { id: 'empty', icon: <FileText size={18} />, dirs: ['README.md', 'CONFIG.json', 'INSTRUCTION.md'] },
8
8
  ];
9
9
 
10
- export const TOTAL_STEPS = 6;
10
+ export const TOTAL_STEPS = 4;
11
11
  export const STEP_KB = 0;
12
- export const STEP_PORTS = 2;
13
- export const STEP_AGENTS = 4;
12
+ export const STEP_AI = 1;
13
+ export const STEP_AGENTS = 2;
14
+ export const STEP_REVIEW = 3;
@@ -6,11 +6,9 @@ import { useLocale } from '@/lib/LocaleContext';
6
6
  import { copyToClipboard } from '@/lib/clipboard';
7
7
  import { toast } from '@/lib/toast';
8
8
  import type { SetupState, PortStatus, AgentEntry, AgentInstallStatus } from './types';
9
- import { TOTAL_STEPS, STEP_KB, STEP_PORTS, STEP_AGENTS } from './constants';
9
+ import { TOTAL_STEPS, STEP_KB, STEP_AI, STEP_AGENTS, STEP_REVIEW } from './constants';
10
10
  import StepKB from './StepKB';
11
11
  import StepAI from './StepAI';
12
- import StepPorts from './StepPorts';
13
- import StepSecurity from './StepSecurity';
14
12
  import StepAgents from './StepAgents';
15
13
  import StepReview from './StepReview';
16
14
  import { RestartButton } from './StepReview';
@@ -122,8 +120,8 @@ export default function SetupWizard() {
122
120
  openaiModel: 'gpt-5.4',
123
121
  openaiBaseUrl: '',
124
122
  openaiKeyMask: '',
125
- webPort: 3000,
126
- mcpPort: 8787,
123
+ webPort: 3456,
124
+ mcpPort: 8781,
127
125
  authToken: '',
128
126
  webPassword: '',
129
127
  });
@@ -184,16 +182,16 @@ export default function SetupWizard() {
184
182
  });
185
183
  }, []);
186
184
 
187
- // Auto-check ports when entering Step 3
185
+ // Auto-check ports when entering AI step (ports are in Advanced section)
188
186
  useEffect(() => {
189
- if (step === STEP_PORTS) {
187
+ if (step === STEP_AI) {
190
188
  checkPort(state.webPort, 'web');
191
189
  checkPort(state.mcpPort, 'mcp');
192
190
  }
193
191
  // eslint-disable-next-line react-hooks/exhaustive-deps
194
192
  }, [step]);
195
193
 
196
- // Load agents when entering Step 5
194
+ // Load agents when entering Agents step
197
195
  useEffect(() => {
198
196
  if (step === STEP_AGENTS && !agentsLoaded && !agentsLoading) {
199
197
  setAgentsLoading(true);
@@ -256,15 +254,17 @@ export default function SetupWizard() {
256
254
  const portConflict = state.webPort === state.mcpPort;
257
255
 
258
256
  const canNext = () => {
259
- if (step === STEP_KB) return state.mindRoot.trim().length > 0;
260
- if (step === STEP_PORTS) {
257
+ if (step === STEP_KB) {
258
+ // KB path required + password required
259
+ return state.mindRoot.trim().length > 0 && state.webPassword.trim().length > 0;
260
+ }
261
+ if (step === STEP_AI) {
262
+ // Ports validation (only when Advanced is open and ports were modified)
261
263
  if (portConflict) return false;
262
264
  if (webPortStatus.checking || mcpPortStatus.checking) return false;
263
- if (webPortStatus.available !== true || mcpPortStatus.available !== true) return false;
264
- return (
265
- state.webPort >= 1024 && state.webPort <= 65535 &&
266
- state.mcpPort >= 1024 && state.mcpPort <= 65535
267
- );
265
+ // Allow next if ports haven't been checked yet (user didn't open Advanced)
266
+ if (webPortStatus.available === false || mcpPortStatus.available === false) return false;
267
+ return true;
268
268
  }
269
269
  return true;
270
270
  };
@@ -359,32 +359,22 @@ export default function SetupWizard() {
359
359
  </div>
360
360
 
361
361
  <div className="flex justify-center">
362
- <StepDots step={step} setStep={setStep} stepTitles={s.stepTitles} disabled={submitting || completed} />
362
+ <StepDots step={step} setStep={setStep} stepTitles={s.stepTitles} disabled={submitting || completed} numberedSteps={STEP_REVIEW} />
363
363
  </div>
364
364
 
365
365
  <h2 className="text-lg font-semibold mb-5" style={{ color: 'var(--foreground)' }}>
366
- {s.stepTitles[step]}
366
+ {step === STEP_REVIEW ? `✓ ${s.stepTitles[step]}` : s.stepTitles[step]}
367
367
  </h2>
368
368
 
369
369
  {step === 0 && <StepKB state={state} update={update} t={t} homeDir={homeDir} />}
370
- {step === 1 && <StepAI state={state} update={update} s={s} />}
371
- {step === 2 && (
372
- <StepPorts
373
- state={state} update={update}
370
+ {step === 1 && (
371
+ <StepAI state={state} update={update} s={s} onCopyToken={copyToken}
374
372
  webPortStatus={webPortStatus} mcpPortStatus={mcpPortStatus}
375
373
  setWebPortStatus={setWebPortStatus} setMcpPortStatus={setMcpPortStatus}
376
- checkPort={checkPort} portConflict={portConflict} s={s}
377
- />
378
- )}
379
- {step === 3 && (
380
- <StepSecurity
381
- authToken={state.authToken}
382
- onCopy={copyToken} onGenerate={generateToken}
383
- webPassword={state.webPassword} onPasswordChange={v => update('webPassword', v)}
384
- s={s}
374
+ checkPort={checkPort} portConflict={portConflict}
385
375
  />
386
376
  )}
387
- {step === 4 && (
377
+ {step === 2 && (
388
378
  <StepAgents
389
379
  agents={agents} agentsLoading={agentsLoading}
390
380
  selectedAgents={selectedAgents} setSelectedAgents={setSelectedAgents}
@@ -394,7 +384,7 @@ export default function SetupWizard() {
394
384
  template={state.template}
395
385
  />
396
386
  )}
397
- {step === 5 && (
387
+ {step === 3 && (
398
388
  <StepReview
399
389
  state={state} selectedAgents={selectedAgents}
400
390
  agentStatuses={agentStatuses} onRetryAgent={retryAgent}