@geminilight/mindos 0.6.30 → 0.6.32

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 (52) hide show
  1. package/README_zh.md +10 -4
  2. package/app/app/api/ask/route.ts +12 -7
  3. package/app/app/api/export/route.ts +105 -0
  4. package/app/app/globals.css +2 -2
  5. package/app/app/trash/page.tsx +7 -0
  6. package/app/app/view/[...path]/ViewPageClient.tsx +234 -2
  7. package/app/components/ExportModal.tsx +220 -0
  8. package/app/components/FileTree.tsx +22 -2
  9. package/app/components/HomeContent.tsx +91 -20
  10. package/app/components/MarkdownView.tsx +45 -10
  11. package/app/components/Sidebar.tsx +10 -1
  12. package/app/components/TrashPageClient.tsx +263 -0
  13. package/app/components/ask/ToolCallBlock.tsx +102 -18
  14. package/app/components/changes/ChangesContentPage.tsx +58 -14
  15. package/app/components/explore/ExploreContent.tsx +4 -7
  16. package/app/components/explore/UseCaseCard.tsx +18 -1
  17. package/app/components/explore/use-cases.generated.ts +76 -0
  18. package/app/components/explore/use-cases.yaml +185 -0
  19. package/app/components/panels/DiscoverPanel.tsx +1 -1
  20. package/app/components/renderers/workflow-yaml/StepEditor.tsx +98 -91
  21. package/app/components/renderers/workflow-yaml/WorkflowEditor.tsx +72 -72
  22. package/app/components/renderers/workflow-yaml/WorkflowRunner.tsx +175 -119
  23. package/app/components/renderers/workflow-yaml/WorkflowYamlRenderer.tsx +61 -61
  24. package/app/components/renderers/workflow-yaml/execution.ts +64 -12
  25. package/app/components/renderers/workflow-yaml/selectors.tsx +65 -13
  26. package/app/components/settings/AiTab.tsx +191 -174
  27. package/app/components/settings/AppearanceTab.tsx +168 -77
  28. package/app/components/settings/KnowledgeTab.tsx +131 -136
  29. package/app/components/settings/McpTab.tsx +11 -11
  30. package/app/components/settings/Primitives.tsx +60 -0
  31. package/app/components/settings/SettingsContent.tsx +15 -8
  32. package/app/components/settings/SyncTab.tsx +12 -12
  33. package/app/components/settings/UninstallTab.tsx +8 -18
  34. package/app/components/settings/UpdateTab.tsx +82 -82
  35. package/app/components/settings/types.ts +17 -8
  36. package/app/lib/acp/session.ts +12 -3
  37. package/app/lib/actions.ts +57 -3
  38. package/app/lib/agent/stream-consumer.ts +18 -0
  39. package/app/lib/agent/tools.ts +56 -9
  40. package/app/lib/core/export.ts +116 -0
  41. package/app/lib/core/trash.ts +241 -0
  42. package/app/lib/fs.ts +47 -0
  43. package/app/lib/hooks/usePinnedFiles.ts +90 -0
  44. package/app/lib/i18n/generated/explore-i18n.generated.ts +138 -0
  45. package/app/lib/i18n/index.ts +3 -0
  46. package/app/lib/i18n/modules/knowledge.ts +120 -6
  47. package/app/lib/i18n/modules/onboarding.ts +2 -134
  48. package/app/lib/i18n/modules/settings.ts +12 -0
  49. package/app/package.json +8 -2
  50. package/app/scripts/generate-explore.ts +145 -0
  51. package/package.json +1 -1
  52. package/app/components/explore/use-cases.ts +0 -58
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
 
3
3
  import { useState, useRef, useCallback, useEffect } from 'react';
4
- import { AlertCircle, ChevronDown, Loader2 } from 'lucide-react';
4
+ import { AlertCircle, ChevronDown, Loader2, Sparkles, Bot, Monitor } from 'lucide-react';
5
5
  import type { AiSettings, AgentSettings, ProviderConfig, SettingsData, AiTabProps } from './types';
6
- import { Field, Select, Input, EnvBadge, ApiKeyInput, Toggle, SectionLabel } from './Primitives';
6
+ import { Field, Select, Input, EnvBadge, ApiKeyInput, Toggle, SettingCard, SettingRow } from './Primitives';
7
7
  import { useLocale } from '@/lib/LocaleContext';
8
8
 
9
9
  type TestState = 'idle' | 'testing' | 'ok' | 'error';
@@ -127,7 +127,7 @@ export function AiTab({ data, updateAi, updateAgent, t }: AiTabProps) {
127
127
  disabled={disabled}
128
128
  title={disabled ? t.hints.testInProgressOrNoKey : undefined}
129
129
  onClick={() => handleTestKey(providerName)}
130
- className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:border-foreground/20 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
130
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg border border-border text-muted-foreground hover:text-foreground hover:border-foreground/20 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
131
131
  >
132
132
  {result.state === 'testing' ? (
133
133
  <>
@@ -151,170 +151,188 @@ export function AiTab({ data, updateAi, updateAgent, t }: AiTabProps) {
151
151
  };
152
152
 
153
153
  return (
154
- <div className="space-y-6">
155
- <Field label={<>{t.settings.ai.provider} <EnvBadge overridden={env.AI_PROVIDER} /></>}>
156
- <Select
157
- value={provider}
158
- onChange={e => updateAi({ provider: e.target.value as 'anthropic' | 'openai' })}
159
- >
160
- <option value="anthropic">Anthropic (Claude)</option>
161
- <option value="openai">OpenAI / compatible</option>
162
- </Select>
163
- </Field>
164
-
165
- {provider === 'anthropic' ? (
166
- <>
167
- <Field label={<>{t.settings.ai.model} <EnvBadge overridden={env.ANTHROPIC_MODEL} /></>}>
168
- <ModelInput
169
- value={anthropic.model}
170
- onChange={v => patchProvider('anthropic', { model: v })}
171
- placeholder={envVal.ANTHROPIC_MODEL || 'claude-sonnet-4-6'}
172
- provider="anthropic"
173
- apiKey={anthropic.apiKey}
174
- envKey={env.ANTHROPIC_API_KEY}
175
- t={t}
176
- />
177
- </Field>
178
- <Field
179
- label={<>{t.settings.ai.apiKey} <EnvBadge overridden={env.ANTHROPIC_API_KEY} /></>}
180
- hint={env.ANTHROPIC_API_KEY ? t.settings.ai.envFieldNote('ANTHROPIC_API_KEY') : t.settings.ai.keyHint}
181
- >
182
- <ApiKeyInput
183
- value={anthropic.apiKey}
184
- onChange={v => patchProvider('anthropic', { apiKey: v })}
185
- />
186
- {renderTestButton('anthropic', !!anthropic.apiKey, !!env.ANTHROPIC_API_KEY)}
187
- </Field>
188
- </>
189
- ) : (
190
- <>
191
- <Field label={<>{t.settings.ai.model} <EnvBadge overridden={env.OPENAI_MODEL} /></>}>
192
- <ModelInput
193
- value={openai.model}
194
- onChange={v => patchProvider('openai', { model: v })}
195
- placeholder={envVal.OPENAI_MODEL || 'gpt-5.4'}
196
- provider="openai"
197
- apiKey={openai.apiKey}
198
- envKey={env.OPENAI_API_KEY}
199
- baseUrl={openai.baseUrl}
200
- t={t}
201
- />
202
- </Field>
203
- <Field
204
- label={<>{t.settings.ai.apiKey} <EnvBadge overridden={env.OPENAI_API_KEY} /></>}
205
- hint={env.OPENAI_API_KEY ? t.settings.ai.envFieldNote('OPENAI_API_KEY') : t.settings.ai.keyHint}
206
- >
207
- <ApiKeyInput
208
- value={openai.apiKey}
209
- onChange={v => patchProvider('openai', { apiKey: v })}
210
- />
211
- {renderTestButton('openai', !!openai.apiKey, !!env.OPENAI_API_KEY)}
212
- </Field>
213
- <Field
214
- label={<>{t.settings.ai.baseUrl} <EnvBadge overridden={env.OPENAI_BASE_URL} /></>}
215
- hint={t.settings.ai.baseUrlHint}
216
- >
217
- <Input
218
- value={openai.baseUrl ?? ''}
219
- onChange={e => patchProvider('openai', { baseUrl: e.target.value })}
220
- placeholder={envVal.OPENAI_BASE_URL || 'https://api.openai.com/v1'}
221
- />
222
- </Field>
223
- </>
224
- )}
225
-
226
- {missingApiKey && (
227
- <div className="flex items-start gap-2 text-xs text-destructive/80 bg-destructive/8 border border-destructive/20 rounded-lg px-3 py-2.5">
228
- <AlertCircle size={13} className="shrink-0 mt-0.5" />
229
- <span>{t.settings.ai.noApiKey}</span>
230
- </div>
231
- )}
232
-
233
- {Object.values(env).some(Boolean) && (
234
- <div className="flex items-start gap-2 text-xs text-[var(--amber)] bg-[var(--amber-subtle)] border border-[var(--amber)]/20 rounded-lg px-3 py-2.5">
235
- <AlertCircle size={13} className="shrink-0 mt-0.5" />
236
- <span>{t.settings.ai.envHint}</span>
154
+ <div className="space-y-4">
155
+ {/* ── Card 1: AI Provider ── */}
156
+ <SettingCard
157
+ icon={<Sparkles size={15} />}
158
+ title={t.settings.ai.provider}
159
+ description={provider === 'anthropic' ? 'Anthropic Claude' : 'OpenAI / compatible'}
160
+ >
161
+ {/* Provider toggle — two clickable mini-cards */}
162
+ <div className="flex gap-2">
163
+ {(['anthropic', 'openai'] as const).map(p => (
164
+ <button
165
+ key={p}
166
+ type="button"
167
+ onClick={() => updateAi({ provider: p })}
168
+ className={`flex-1 flex items-center gap-2.5 px-3.5 py-2.5 rounded-lg border transition-all ${
169
+ provider === p
170
+ ? 'border-[var(--amber)] bg-[var(--amber-subtle)] shadow-sm'
171
+ : 'border-border/50 hover:border-border hover:bg-muted/30'
172
+ }`}
173
+ >
174
+ <span className={`text-sm font-medium ${provider === p ? 'text-foreground' : 'text-muted-foreground'}`}>
175
+ {p === 'anthropic' ? 'Anthropic' : 'OpenAI'}
176
+ </span>
177
+ <EnvBadge overridden={env.AI_PROVIDER} />
178
+ </button>
179
+ ))}
237
180
  </div>
238
- )}
239
-
240
- {/* Agent Behavior */}
241
- <div className="pt-3 border-t border-border">
242
- <h3 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-3">{t.settings.agent.title}</h3>
243
181
 
244
- <div className="space-y-4">
245
- <Field label={t.settings.agent.maxSteps} hint={t.settings.agent.maxStepsHint}>
246
- <Select
247
- value={String(data.agent?.maxSteps ?? 20)}
248
- onChange={e => updateAgent({ maxSteps: Number(e.target.value) })}
182
+ {/* Model + API Key */}
183
+ {provider === 'anthropic' ? (
184
+ <>
185
+ <Field label={<>{t.settings.ai.model} <EnvBadge overridden={env.ANTHROPIC_MODEL} /></>}>
186
+ <ModelInput
187
+ value={anthropic.model}
188
+ onChange={v => patchProvider('anthropic', { model: v })}
189
+ placeholder={envVal.ANTHROPIC_MODEL || 'claude-sonnet-4-6'}
190
+ provider="anthropic"
191
+ apiKey={anthropic.apiKey}
192
+ envKey={env.ANTHROPIC_API_KEY}
193
+ t={t}
194
+ />
195
+ </Field>
196
+ <Field
197
+ label={<>{t.settings.ai.apiKey} <EnvBadge overridden={env.ANTHROPIC_API_KEY} /></>}
198
+ hint={env.ANTHROPIC_API_KEY ? t.settings.ai.envFieldNote('ANTHROPIC_API_KEY') : t.settings.ai.keyHint}
249
199
  >
250
- <option value="5">5</option>
251
- <option value="10">10</option>
252
- <option value="15">15</option>
253
- <option value="20">20</option>
254
- <option value="25">25</option>
255
- <option value="30">30</option>
256
- </Select>
257
- </Field>
258
-
259
- <Field label={t.settings.agent.contextStrategy} hint={t.settings.agent.contextStrategyHint}>
260
- <Select
261
- value={data.agent?.contextStrategy ?? 'auto'}
262
- onChange={e => updateAgent({ contextStrategy: e.target.value as 'auto' | 'off' })}
200
+ <ApiKeyInput
201
+ value={anthropic.apiKey}
202
+ onChange={v => patchProvider('anthropic', { apiKey: v })}
203
+ />
204
+ {renderTestButton('anthropic', !!anthropic.apiKey, !!env.ANTHROPIC_API_KEY)}
205
+ </Field>
206
+ </>
207
+ ) : (
208
+ <>
209
+ <Field label={<>{t.settings.ai.model} <EnvBadge overridden={env.OPENAI_MODEL} /></>}>
210
+ <ModelInput
211
+ value={openai.model}
212
+ onChange={v => patchProvider('openai', { model: v })}
213
+ placeholder={envVal.OPENAI_MODEL || 'gpt-5.4'}
214
+ provider="openai"
215
+ apiKey={openai.apiKey}
216
+ envKey={env.OPENAI_API_KEY}
217
+ baseUrl={openai.baseUrl}
218
+ t={t}
219
+ />
220
+ </Field>
221
+ <Field
222
+ label={<>{t.settings.ai.apiKey} <EnvBadge overridden={env.OPENAI_API_KEY} /></>}
223
+ hint={env.OPENAI_API_KEY ? t.settings.ai.envFieldNote('OPENAI_API_KEY') : t.settings.ai.keyHint}
263
224
  >
264
- <option value="auto">{t.settings.agent.contextStrategyAuto}</option>
265
- <option value="off">{t.settings.agent.contextStrategyOff}</option>
266
- </Select>
267
- </Field>
268
-
269
- <Field label={t.settings.agent.reconnectRetries} hint={t.settings.agent.reconnectRetriesHint}>
270
- <Select
271
- value={String(data.agent?.reconnectRetries ?? 3)}
272
- onChange={e => {
273
- const v = Number(e.target.value);
274
- updateAgent({ reconnectRetries: v });
275
- try { localStorage.setItem('mindos-reconnect-retries', String(v)); } catch (err) { console.warn("[AiTab] localStorage setItem reconnectRetries failed:", err); }
276
- }}
225
+ <ApiKeyInput
226
+ value={openai.apiKey}
227
+ onChange={v => patchProvider('openai', { apiKey: v })}
228
+ />
229
+ {renderTestButton('openai', !!openai.apiKey, !!env.OPENAI_API_KEY)}
230
+ </Field>
231
+ <Field
232
+ label={<>{t.settings.ai.baseUrl} <EnvBadge overridden={env.OPENAI_BASE_URL} /></>}
233
+ hint={t.settings.ai.baseUrlHint}
277
234
  >
278
- <option value="0">Off</option>
279
- <option value="1">1</option>
280
- <option value="2">2</option>
281
- <option value="3">3</option>
282
- <option value="5">5</option>
283
- <option value="10">10</option>
284
- </Select>
285
- </Field>
286
-
287
- {provider === 'anthropic' && (
288
- <>
289
- <div className="flex items-center justify-between">
290
- <div>
291
- <div className="text-sm text-foreground">{t.settings.agent.thinking}</div>
292
- <div className="text-xs text-muted-foreground mt-0.5">{t.settings.agent.thinkingHint}</div>
293
- </div>
294
- <Toggle checked={data.agent?.enableThinking ?? false} onChange={() => updateAgent({ enableThinking: !(data.agent?.enableThinking ?? false) })} />
295
- </div>
296
-
297
- {data.agent?.enableThinking && (
298
- <Field label={t.settings.agent.thinkingBudget} hint={t.settings.agent.thinkingBudgetHint}>
299
- <Input
300
- type="number"
301
- value={String(data.agent?.thinkingBudget ?? 5000)}
302
- onChange={e => {
303
- const v = parseInt(e.target.value, 10);
304
- if (!isNaN(v)) updateAgent({ thinkingBudget: Math.max(1000, Math.min(50000, v)) });
305
- }}
306
- min={1000}
307
- max={50000}
308
- step={1000}
309
- />
310
- </Field>
311
- )}
312
- </>
313
- )}
314
- </div>
315
- </div>
235
+ <Input
236
+ value={openai.baseUrl ?? ''}
237
+ onChange={e => patchProvider('openai', { baseUrl: e.target.value })}
238
+ placeholder={envVal.OPENAI_BASE_URL || 'https://api.openai.com/v1'}
239
+ />
240
+ </Field>
241
+ </>
242
+ )}
243
+
244
+ {/* Inline warnings */}
245
+ {missingApiKey && (
246
+ <div className="flex items-start gap-2 text-xs text-destructive/80 bg-destructive/8 border border-destructive/20 rounded-lg px-3 py-2.5">
247
+ <AlertCircle size={13} className="shrink-0 mt-0.5" />
248
+ <span>{t.settings.ai.noApiKey}</span>
249
+ </div>
250
+ )}
251
+ {Object.values(env).some(Boolean) && (
252
+ <div className="flex items-start gap-2 text-xs text-[var(--amber)] bg-[var(--amber-subtle)] border border-[var(--amber)]/20 rounded-lg px-3 py-2.5">
253
+ <AlertCircle size={13} className="shrink-0 mt-0.5" />
254
+ <span>{t.settings.ai.envHint}</span>
255
+ </div>
256
+ )}
257
+ </SettingCard>
258
+
259
+ {/* ── Card 2: Agent Behavior ── */}
260
+ <SettingCard
261
+ icon={<Bot size={15} />}
262
+ title={t.settings.agent.title}
263
+ description={t.settings.agent.subtitle ?? 'Configure how the AI agent operates'}
264
+ >
265
+ <SettingRow label={t.settings.agent.maxSteps} hint={t.settings.agent.maxStepsHint}>
266
+ <Select
267
+ value={String(data.agent?.maxSteps ?? 20)}
268
+ onChange={e => updateAgent({ maxSteps: Number(e.target.value) })}
269
+ className="w-20"
270
+ >
271
+ <option value="5">5</option>
272
+ <option value="10">10</option>
273
+ <option value="15">15</option>
274
+ <option value="20">20</option>
275
+ <option value="25">25</option>
276
+ <option value="30">30</option>
277
+ </Select>
278
+ </SettingRow>
316
279
 
317
- {/* Ask AI Display Mode */}
280
+ <SettingRow label={t.settings.agent.contextStrategy} hint={t.settings.agent.contextStrategyHint}>
281
+ <Select
282
+ value={data.agent?.contextStrategy ?? 'auto'}
283
+ onChange={e => updateAgent({ contextStrategy: e.target.value as 'auto' | 'off' })}
284
+ className="w-24"
285
+ >
286
+ <option value="auto">{t.settings.agent.contextStrategyAuto}</option>
287
+ <option value="off">{t.settings.agent.contextStrategyOff}</option>
288
+ </Select>
289
+ </SettingRow>
290
+
291
+ <SettingRow label={t.settings.agent.reconnectRetries} hint={t.settings.agent.reconnectRetriesHint}>
292
+ <Select
293
+ value={String(data.agent?.reconnectRetries ?? 3)}
294
+ onChange={e => {
295
+ const v = Number(e.target.value);
296
+ updateAgent({ reconnectRetries: v });
297
+ try { localStorage.setItem('mindos-reconnect-retries', String(v)); } catch (err) { console.warn("[AiTab] localStorage setItem reconnectRetries failed:", err); }
298
+ }}
299
+ className="w-20"
300
+ >
301
+ <option value="0">Off</option>
302
+ <option value="1">1</option>
303
+ <option value="2">2</option>
304
+ <option value="3">3</option>
305
+ <option value="5">5</option>
306
+ <option value="10">10</option>
307
+ </Select>
308
+ </SettingRow>
309
+
310
+ {provider === 'anthropic' && (
311
+ <>
312
+ <SettingRow label={t.settings.agent.thinking} hint={t.settings.agent.thinkingHint}>
313
+ <Toggle checked={data.agent?.enableThinking ?? false} onChange={() => updateAgent({ enableThinking: !(data.agent?.enableThinking ?? false) })} />
314
+ </SettingRow>
315
+
316
+ {data.agent?.enableThinking && (
317
+ <Field label={t.settings.agent.thinkingBudget} hint={t.settings.agent.thinkingBudgetHint}>
318
+ <Input
319
+ type="number"
320
+ value={String(data.agent?.thinkingBudget ?? 5000)}
321
+ onChange={e => {
322
+ const v = parseInt(e.target.value, 10);
323
+ if (!isNaN(v)) updateAgent({ thinkingBudget: Math.max(1000, Math.min(50000, v)) });
324
+ }}
325
+ min={1000}
326
+ max={50000}
327
+ step={1000}
328
+ />
329
+ </Field>
330
+ )}
331
+ </>
332
+ )}
333
+ </SettingCard>
334
+
335
+ {/* ── Card 3: Display Mode ── */}
318
336
  <AskDisplayMode />
319
337
  </div>
320
338
  );
@@ -395,7 +413,7 @@ function ModelInput({
395
413
  disabled={!hasKey || loading}
396
414
  onClick={fetchModels}
397
415
  title={t.settings.ai.listModels}
398
- className="inline-flex items-center gap-1 px-2 py-1 text-xs rounded-lg border border-border text-muted-foreground hover:text-foreground hover:border-foreground/20 transition-colors disabled:opacity-40 disabled:cursor-not-allowed shrink-0"
416
+ className="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm rounded-lg border border-border text-muted-foreground hover:text-foreground hover:border-foreground/20 transition-colors disabled:opacity-40 disabled:cursor-not-allowed shrink-0"
399
417
  >
400
418
  {loading ? <Loader2 size={12} className="animate-spin" /> : <ChevronDown size={12} />}
401
419
  {t.settings.ai.listModels}
@@ -408,7 +426,7 @@ function ModelInput({
408
426
  <button
409
427
  key={m}
410
428
  type="button"
411
- className={`w-full text-left px-3 py-1.5 text-xs hover:bg-accent transition-colors ${m === value ? 'bg-accent/60 font-medium' : ''}`}
429
+ className={`w-full text-left px-3 py-1.5 text-sm hover:bg-accent transition-colors ${m === value ? 'bg-accent/60 font-medium' : ''}`}
412
430
  onClick={() => { onChange(m); setOpen(false); }}
413
431
  >
414
432
  {m}
@@ -447,16 +465,15 @@ function AskDisplayMode() {
447
465
  };
448
466
 
449
467
  return (
450
- <div className="pt-3 border-t border-border">
451
- <SectionLabel>MindOS Agent</SectionLabel>
452
- <div className="space-y-4">
453
- <Field label={t.settings.askDisplayMode?.label ?? 'Display Mode'} hint={t.settings.askDisplayMode?.hint ?? 'Side panel stays docked on the right. Popup opens a floating dialog.'}>
454
- <Select value={mode} onChange={e => handleChange(e.target.value)}>
455
- <option value="panel">{t.settings.askDisplayMode?.panel ?? 'Side Panel'}</option>
456
- <option value="popup">{t.settings.askDisplayMode?.popup ?? 'Popup'}</option>
457
- </Select>
458
- </Field>
459
- </div>
460
- </div>
468
+ <SettingCard
469
+ icon={<Monitor size={15} />}
470
+ title={t.settings.askDisplayMode?.label ?? 'Display Mode'}
471
+ description={t.settings.askDisplayMode?.hint ?? 'Side panel stays docked on the right. Popup opens a floating dialog.'}
472
+ >
473
+ <Select value={mode} onChange={e => handleChange(e.target.value)}>
474
+ <option value="panel">{t.settings.askDisplayMode?.panel ?? 'Side Panel'}</option>
475
+ <option value="popup">{t.settings.askDisplayMode?.popup ?? 'Popup'}</option>
476
+ </Select>
477
+ </SettingCard>
461
478
  );
462
479
  }