@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.
- package/README_zh.md +10 -4
- package/app/app/api/ask/route.ts +12 -7
- package/app/app/api/export/route.ts +105 -0
- package/app/app/globals.css +2 -2
- package/app/app/trash/page.tsx +7 -0
- package/app/app/view/[...path]/ViewPageClient.tsx +234 -2
- package/app/components/ExportModal.tsx +220 -0
- package/app/components/FileTree.tsx +22 -2
- package/app/components/HomeContent.tsx +91 -20
- package/app/components/MarkdownView.tsx +45 -10
- package/app/components/Sidebar.tsx +10 -1
- package/app/components/TrashPageClient.tsx +263 -0
- package/app/components/ask/ToolCallBlock.tsx +102 -18
- package/app/components/changes/ChangesContentPage.tsx +58 -14
- package/app/components/explore/ExploreContent.tsx +4 -7
- package/app/components/explore/UseCaseCard.tsx +18 -1
- package/app/components/explore/use-cases.generated.ts +76 -0
- package/app/components/explore/use-cases.yaml +185 -0
- package/app/components/panels/DiscoverPanel.tsx +1 -1
- package/app/components/renderers/workflow-yaml/StepEditor.tsx +98 -91
- package/app/components/renderers/workflow-yaml/WorkflowEditor.tsx +72 -72
- package/app/components/renderers/workflow-yaml/WorkflowRunner.tsx +175 -119
- package/app/components/renderers/workflow-yaml/WorkflowYamlRenderer.tsx +61 -61
- package/app/components/renderers/workflow-yaml/execution.ts +64 -12
- package/app/components/renderers/workflow-yaml/selectors.tsx +65 -13
- package/app/components/settings/AiTab.tsx +191 -174
- package/app/components/settings/AppearanceTab.tsx +168 -77
- package/app/components/settings/KnowledgeTab.tsx +131 -136
- package/app/components/settings/McpTab.tsx +11 -11
- package/app/components/settings/Primitives.tsx +60 -0
- package/app/components/settings/SettingsContent.tsx +15 -8
- package/app/components/settings/SyncTab.tsx +12 -12
- package/app/components/settings/UninstallTab.tsx +8 -18
- package/app/components/settings/UpdateTab.tsx +82 -82
- package/app/components/settings/types.ts +17 -8
- package/app/lib/acp/session.ts +12 -3
- package/app/lib/actions.ts +57 -3
- package/app/lib/agent/stream-consumer.ts +18 -0
- package/app/lib/agent/tools.ts +56 -9
- package/app/lib/core/export.ts +116 -0
- package/app/lib/core/trash.ts +241 -0
- package/app/lib/fs.ts +47 -0
- package/app/lib/hooks/usePinnedFiles.ts +90 -0
- package/app/lib/i18n/generated/explore-i18n.generated.ts +138 -0
- package/app/lib/i18n/index.ts +3 -0
- package/app/lib/i18n/modules/knowledge.ts +120 -6
- package/app/lib/i18n/modules/onboarding.ts +2 -134
- package/app/lib/i18n/modules/settings.ts +12 -0
- package/app/package.json +8 -2
- package/app/scripts/generate-explore.ts +145 -0
- package/package.json +1 -1
- 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,
|
|
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-
|
|
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-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
<
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
<
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
<
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
-
<
|
|
451
|
-
<
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
}
|