@marimo-team/islands 0.25.3-dev221 → 0.25.3-dev223

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 (28) hide show
  1. package/dist/{ErrorBoundary-B5GJid5Z.js → ErrorBoundary-BToheQV2.js} +1 -1
  2. package/dist/{code-visibility-CYzEUXNB.js → code-visibility-CO0hOr56.js} +1 -1
  3. package/dist/{common-CbUT6FYs.js → common-CV0669k1.js} +1 -1
  4. package/dist/{data-grid-overlay-editor-30GRNiqG.js → data-grid-overlay-editor-CwDPMtXI.js} +1 -1
  5. package/dist/{feedback-button-r2frGdMW.js → feedback-button-RB_OfVUt.js} +2 -2
  6. package/dist/{glide-data-editor-Dnne6LfS.js → glide-data-editor-B2h5fYfH.js} +3 -3
  7. package/dist/{globals-BnVQfyqL.js → globals-YR9OeOwq.js} +1 -1
  8. package/dist/main.js +4206 -4206
  9. package/dist/{number-overlay-editor-CpnDV4Mb.js → number-overlay-editor-BY4yYxfZ.js} +1 -1
  10. package/dist/{reveal-component-Bu044POK.js → reveal-component-GiGQpBMz.js} +2 -2
  11. package/dist/style.css +1 -1
  12. package/package.json +1 -1
  13. package/src/components/app-config/ai-config.tsx +148 -147
  14. package/src/components/app-config/app-config-button.tsx +6 -5
  15. package/src/components/app-config/common.tsx +7 -9
  16. package/src/components/app-config/data-form.tsx +112 -113
  17. package/src/components/app-config/mcp-config.tsx +30 -53
  18. package/src/components/app-config/user-config-form.css +91 -0
  19. package/src/components/app-config/user-config-form.tsx +236 -298
  20. package/src/components/editor/actions/__tests__/pair-with-agent-modal.test.tsx +9 -5
  21. package/src/components/editor/actions/__tests__/pair-with-agent.test.tsx +3 -3
  22. package/src/components/editor/actions/pair-with-agent-modal.tsx +5 -5
  23. package/src/components/editor/actions/pair-with-agent.tsx +10 -7
  24. package/src/core/cells/__tests__/session.test.ts +4 -4
  25. package/src/core/cells/session.ts +1 -1
  26. package/src/core/meta/__tests__/code-visibility.test.tsx +1 -1
  27. package/src/core/notebook/document/__tests__/fixtures.ts +1 -1
  28. package/src/core/notebook/document/__tests__/test-host.ts +4 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marimo-team/islands",
3
- "version": "0.25.3-dev221",
3
+ "version": "0.25.3-dev223",
4
4
  "main": "dist/main.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -119,7 +119,7 @@ export const AiProviderTitle: React.FC<AiProviderTitleProps> = ({
119
119
  children,
120
120
  }) => {
121
121
  return (
122
- <div className="flex items-center text-base font-semibold">
122
+ <div className="flex items-center text-sm font-medium">
123
123
  {provider && <AiProviderIcon provider={provider} className="mr-2" />}
124
124
  {children}
125
125
  </div>
@@ -149,9 +149,10 @@ export const ApiKey: React.FC<ApiKeyProps> = ({
149
149
  control={form.control}
150
150
  name={name}
151
151
  render={({ field, override }) => (
152
- <div className="flex flex-col space-y-1">
152
+ <div className="settings-field flex flex-col space-y-1">
153
153
  <FormItem className={formItemClasses}>
154
- <FormLabel>API Key</FormLabel>
154
+ <FormLabel>API key</FormLabel>
155
+ {description && <FormDescription>{description}</FormDescription>}
155
156
  <FormControl>
156
157
  <Input
157
158
  data-testid={testId}
@@ -174,7 +175,6 @@ export const ApiKey: React.FC<ApiKeyProps> = ({
174
175
  <FormMessage />
175
176
  <IsOverridden override={override} />
176
177
  </FormItem>
177
- {description && <FormDescription>{description}</FormDescription>}
178
178
  </div>
179
179
  )}
180
180
  />
@@ -218,9 +218,10 @@ export const BaseUrl: React.FC<BaseUrlProps> = ({
218
218
  control={form.control}
219
219
  name={name}
220
220
  render={({ field, override }) => (
221
- <div className="flex flex-col space-y-1">
221
+ <div className="settings-field flex flex-col space-y-1">
222
222
  <FormItem className={formItemClasses}>
223
223
  <FormLabel>Base URL</FormLabel>
224
+ {description && <FormDescription>{description}</FormDescription>}
224
225
  <FormControl>
225
226
  <Input
226
227
  data-testid={testId}
@@ -239,7 +240,6 @@ export const BaseUrl: React.FC<BaseUrlProps> = ({
239
240
  <FormMessage />
240
241
  <IsOverridden override={override} />
241
242
  </FormItem>
242
- {description && <FormDescription>{description}</FormDescription>}
243
243
  </div>
244
244
  )}
245
245
  />
@@ -283,6 +283,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
283
283
  const renderFormItem = () => (
284
284
  <FormItem className={formItemClasses}>
285
285
  <FormLabel>{label}</FormLabel>
286
+ {description && <FormDescription>{description}</FormDescription>}
286
287
  <FormControl>
287
288
  <AIModelDropdown
288
289
  value={value}
@@ -325,11 +326,10 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
325
326
  );
326
327
 
327
328
  return (
328
- <div className="flex flex-col space-y-1">
329
+ <div className="settings-field flex flex-col space-y-1">
329
330
  {renderFormItem()}
330
331
  <IsOverridden override={override} />
331
332
  <IncorrectModelId value={value} />
332
- {description && <FormDescription>{description}</FormDescription>}
333
333
  </div>
334
334
  );
335
335
  }}
@@ -344,6 +344,7 @@ interface ProviderSelectProps {
344
344
  options: string[];
345
345
  testId: string;
346
346
  disabled?: boolean;
347
+ description?: React.ReactNode;
347
348
  }
348
349
 
349
350
  export const ProviderSelect: React.FC<ProviderSelectProps> = ({
@@ -352,15 +353,17 @@ export const ProviderSelect: React.FC<ProviderSelectProps> = ({
352
353
  options,
353
354
  testId,
354
355
  disabled = false,
356
+ description,
355
357
  }) => {
356
358
  return (
357
359
  <OverriddenFormField
358
360
  control={form.control}
359
361
  name={name}
360
362
  render={({ field, override }) => (
361
- <div className="flex flex-col space-y-1">
363
+ <div className="settings-field flex flex-col space-y-1">
362
364
  <FormItem className={formItemClasses}>
363
365
  <FormLabel>Provider</FormLabel>
366
+ {description && <FormDescription>{description}</FormDescription>}
364
367
  <FormControl>
365
368
  <NativeSelect
366
369
  data-testid={testId}
@@ -440,7 +443,7 @@ const renderCopilotProvider = ({
440
443
  if (copilot === "custom") {
441
444
  return (
442
445
  <ModelSelector
443
- label="Autocomplete Model"
446
+ label="Autocomplete model"
444
447
  form={form}
445
448
  config={config}
446
449
  name="ai.models.autocomplete_model"
@@ -461,7 +464,9 @@ const SettingGroup = ({
461
464
  className?: string;
462
465
  }) => {
463
466
  return (
464
- <div className={cn("flex flex-col gap-4 pb-4", className)}>{children}</div>
467
+ <div className={cn("settings-group flex flex-col gap-4", className)}>
468
+ {children}
469
+ </div>
465
470
  );
466
471
  };
467
472
 
@@ -563,18 +568,14 @@ export const AiCodeCompletionConfig: React.FC<AiConfigProps> = ({
563
568
  const { value: copilot } = getOverride("completion.copilot", watchedCopilot);
564
569
  return (
565
570
  <SettingGroup>
566
- <SettingSubtitle>Code Completion</SettingSubtitle>
567
- <p className="text-sm text-muted-secondary">
568
- Choose GitHub Copilot, Codeium, or a custom provider (such as Ollama) to
569
- enable AI-powered code completion.
570
- </p>
571
-
571
+ <SettingSubtitle>Copilot</SettingSubtitle>
572
572
  <ProviderSelect
573
573
  form={form}
574
574
  config={config}
575
575
  name="completion.copilot"
576
576
  options={["none", "github", "codeium", "custom"]}
577
577
  testId="copilot-select"
578
+ description="Choose GitHub Copilot, Codeium, or a custom provider (such as Ollama) to enable AI-powered code completion."
578
579
  />
579
580
 
580
581
  {renderCopilotProvider({ form, config, onSubmit, copilot })}
@@ -696,7 +697,7 @@ export const CustomProvidersConfig: React.FC<AiConfigProps> = ({
696
697
  const providerForm = (
697
698
  <div className="flex flex-col gap-3 p-4 border border-border rounded-md bg-muted/20">
698
699
  <div className="flex flex-col gap-1.5">
699
- <Label htmlFor={providerNameInputId}>Provider Name</Label>
700
+ <Label htmlFor={providerNameInputId}>Provider name</Label>
700
701
  <Input
701
702
  id={providerNameInputId}
702
703
  placeholder="e.g., together, groq, mistral"
@@ -734,7 +735,7 @@ export const CustomProvidersConfig: React.FC<AiConfigProps> = ({
734
735
  </div>
735
736
 
736
737
  <div className="flex flex-col gap-1.5">
737
- <Label htmlFor={apiKeyInputId}>API Key (optional)</Label>
738
+ <Label htmlFor={apiKeyInputId}>API key (optional)</Label>
738
739
  <Input
739
740
  id={apiKeyInputId}
740
741
  placeholder="sk-..."
@@ -750,7 +751,7 @@ export const CustomProvidersConfig: React.FC<AiConfigProps> = ({
750
751
  disabled={!hasValidValues}
751
752
  size="xs"
752
753
  >
753
- Add Provider
754
+ Add provider
754
755
  </Button>
755
756
  <Button variant="outline" onClick={resetForm} size="xs">
756
757
  Cancel
@@ -839,7 +840,7 @@ export const CustomProvidersConfig: React.FC<AiConfigProps> = ({
839
840
  className="w-fit self-end"
840
841
  >
841
842
  <Trash2Icon className="h-4 w-4 mr-2" />
842
- Remove Provider
843
+ Remove provider
843
844
  </Button>
844
845
  </AccordionFormItem>
845
846
  );
@@ -847,10 +848,10 @@ export const CustomProvidersConfig: React.FC<AiConfigProps> = ({
847
848
 
848
849
  return (
849
850
  <SettingGroup>
850
- <SettingSubtitle>Custom Providers</SettingSubtitle>
851
+ <SettingSubtitle>Custom providers</SettingSubtitle>
851
852
  <p className="text-sm text-muted-secondary">
852
853
  Add your own OpenAI-compatible provider. Once added, you can
853
- configure models in the AI Models tab.
854
+ configure models in the AI models tab.
854
855
  </p>
855
856
 
856
857
  {customProviderEntries.length > 0 && (
@@ -872,7 +873,7 @@ export const CustomProvidersConfig: React.FC<AiConfigProps> = ({
872
873
  className="self-start"
873
874
  isFormOpen={isAddingProvider}
874
875
  setIsFormOpen={setIsAddingProvider}
875
- label="Add Provider"
876
+ label="Add provider"
876
877
  />
877
878
  )}
878
879
  </SettingGroup>
@@ -1208,9 +1209,12 @@ export const AiProvidersConfig: React.FC<AiConfigProps> = ({
1208
1209
  disabled={isWasmRuntime}
1209
1210
  name="ai.bedrock.region_name"
1210
1211
  render={({ field, override }) => (
1211
- <div className="flex flex-col space-y-1">
1212
+ <div className="settings-field flex flex-col space-y-1">
1212
1213
  <FormItem className={formItemClasses}>
1213
- <FormLabel>AWS Region</FormLabel>
1214
+ <FormLabel>AWS region</FormLabel>
1215
+ <FormDescription>
1216
+ The AWS region where Bedrock service is available.
1217
+ </FormDescription>
1214
1218
  <FormControl>
1215
1219
  <NativeSelect
1216
1220
  data-testid="bedrock-region-select"
@@ -1233,9 +1237,6 @@ export const AiProvidersConfig: React.FC<AiConfigProps> = ({
1233
1237
  <FormMessage />
1234
1238
  <IsOverridden override={override} />
1235
1239
  </FormItem>
1236
- <FormDescription>
1237
- The AWS region where Bedrock service is available.
1238
- </FormDescription>
1239
1240
  </div>
1240
1241
  )}
1241
1242
  />
@@ -1245,9 +1246,13 @@ export const AiProvidersConfig: React.FC<AiConfigProps> = ({
1245
1246
  disabled={isWasmRuntime}
1246
1247
  name="ai.bedrock.profile_name"
1247
1248
  render={({ field, override }) => (
1248
- <div className="flex flex-col space-y-1">
1249
+ <div className="settings-field flex flex-col space-y-1">
1249
1250
  <FormItem className={formItemClasses}>
1250
- <FormLabel>AWS Profile Name (Optional)</FormLabel>
1251
+ <FormLabel>AWS profile name (optional)</FormLabel>
1252
+ <FormDescription>
1253
+ The AWS profile name from your ~/.aws/credentials file.
1254
+ Leave blank to use your default AWS credentials.
1255
+ </FormDescription>
1251
1256
  <FormControl>
1252
1257
  <Input
1253
1258
  data-testid="bedrock-profile-input"
@@ -1262,17 +1267,13 @@ export const AiProvidersConfig: React.FC<AiConfigProps> = ({
1262
1267
  <FormMessage />
1263
1268
  <IsOverridden override={override} />
1264
1269
  </FormItem>
1265
- <FormDescription>
1266
- The AWS profile name from your ~/.aws/credentials file. Leave
1267
- blank to use your default AWS credentials.
1268
- </FormDescription>
1269
1270
  </div>
1270
1271
  )}
1271
1272
  />
1272
1273
  </AccordionFormItem>
1273
1274
 
1274
1275
  <AccordionFormItem
1275
- title="OpenAI-Compatible (Legacy)"
1276
+ title="OpenAI-compatible (legacy)"
1276
1277
  provider="openai-compatible"
1277
1278
  isConfigured={
1278
1279
  hasValue("ai.open_ai_compatible.api_key") &&
@@ -1280,7 +1281,7 @@ export const AiProvidersConfig: React.FC<AiConfigProps> = ({
1280
1281
  }
1281
1282
  >
1282
1283
  <p className="text-sm text-amber-600 dark:text-amber-400 mb-2">
1283
- Consider using Custom Providers instead, which allows you to add
1284
+ Consider using Custom providers instead, which allows you to add
1284
1285
  multiple providers with distinct names.
1285
1286
  </p>
1286
1287
  <ApiKey
@@ -1326,98 +1327,11 @@ export const AiAssistConfig: React.FC<AiConfigProps> = ({
1326
1327
 
1327
1328
  return (
1328
1329
  <SettingGroup>
1329
- <SettingSubtitle>AI Assistant</SettingSubtitle>
1330
-
1331
- <FormField
1332
- control={form.control}
1333
- name="ai.inline_tooltip"
1334
- render={({ field }) => (
1335
- <div className="flex flex-col gap-y-1">
1336
- <FormItem className={formItemClasses}>
1337
- <FormLabel className="font-normal">AI Edit Tooltip</FormLabel>
1338
- <FormControl>
1339
- <Checkbox
1340
- data-testid="inline-ai-checkbox"
1341
- checked={field.value === true}
1342
- onCheckedChange={field.onChange}
1343
- />
1344
- </FormControl>
1345
- </FormItem>
1346
- <FormDescription>
1347
- Enable "Edit with AI" tooltip when selecting code.
1348
- </FormDescription>
1349
- </div>
1350
- )}
1351
- />
1352
-
1353
- <FormField
1354
- control={form.control}
1355
- name="ai.max_tokens"
1356
- render={({ field }) => {
1357
- return (
1358
- <div className="flex flex-col gap-y-1">
1359
- <div className="flex items-center gap-x-2">
1360
- <FormItem className={formItemClasses}>
1361
- <FormLabel className="font-normal">
1362
- Max output tokens
1363
- </FormLabel>
1364
- <FormControl>
1365
- <Input
1366
- data-testid="ai-max-tokens-input"
1367
- type="number"
1368
- min={1}
1369
- disabled={!maxTokensEnabled}
1370
- className="w-28 h-6"
1371
- value={field.value ?? (maxTokensEnabled ? "" : 32768)}
1372
- onChange={(e) => {
1373
- const n = Number.parseInt(e.target.value, 10);
1374
- field.onChange(Number.isFinite(n) && n > 0 ? n : null);
1375
- }}
1376
- />
1377
- </FormControl>
1378
- </FormItem>
1379
- <FormItem className={formItemClasses}>
1380
- <Checkbox
1381
- data-testid="ai-max-tokens-checkbox"
1382
- checked={maxTokensEnabled}
1383
- onCheckedChange={(checked) => {
1384
- const isChecked = checked === true;
1385
- setMaxTokensEnabled(isChecked);
1386
- // null signals delete to the server; cast because
1387
- // UserConfig (OpenAPI-derived) types max_tokens as
1388
- // `number | undefined`, but zod accepts `null`.
1389
- const next = (
1390
- isChecked ? (field.value ?? 32768) : null
1391
- ) as number | undefined;
1392
- // shouldDirty: true forces RHF to keep this in
1393
- // dirtyFields even when `next` happens to equal the
1394
- // form's defaultValue (e.g. untick → tick when disk
1395
- // started with 32768). Otherwise getDirtyValues
1396
- // would skip it and the save body would be empty.
1397
- form.setValue("ai.max_tokens", next, {
1398
- shouldDirty: true,
1399
- shouldTouch: true,
1400
- });
1401
- onSubmit(form.getValues());
1402
- }}
1403
- />
1404
- <FormLabel className="font-normal">Override</FormLabel>
1405
- </FormItem>
1406
- </div>
1407
-
1408
- <FormDescription>
1409
- Each provider sets its own max output tokens (Anthropic uses a
1410
- recommended default). Adjust to control costs or enable more
1411
- output.
1412
- </FormDescription>
1413
- </div>
1414
- );
1415
- }}
1416
- />
1330
+ <SettingSubtitle>AI assistant</SettingSubtitle>
1417
1331
 
1418
1332
  <FormErrorsBanner />
1419
1333
  <ModelSelector
1420
- label="Chat Model"
1334
+ label="Chat model"
1421
1335
  form={form}
1422
1336
  config={config}
1423
1337
  name="ai.models.chat_model"
@@ -1429,7 +1343,7 @@ export const AiAssistConfig: React.FC<AiConfigProps> = ({
1429
1343
  onSubmit={onSubmit}
1430
1344
  />
1431
1345
  <ModelSelector
1432
- label="Edit Model"
1346
+ label="Edit model"
1433
1347
  form={form}
1434
1348
  config={config}
1435
1349
  name="ai.models.edit_model"
@@ -1444,13 +1358,80 @@ export const AiAssistConfig: React.FC<AiConfigProps> = ({
1444
1358
  onSubmit={onSubmit}
1445
1359
  />
1446
1360
 
1361
+ <FormField
1362
+ control={form.control}
1363
+ name="ai.max_tokens"
1364
+ render={({ field }) => {
1365
+ return (
1366
+ <div className="settings-field flex flex-col gap-y-1">
1367
+ <FormItem className={formItemClasses}>
1368
+ <FormLabel className="font-normal">Max output tokens</FormLabel>
1369
+ <FormDescription>
1370
+ Limit output tokens for both chat and code edits. When not
1371
+ overridden, each provider uses its default (Anthropic uses a
1372
+ recommended default).
1373
+ </FormDescription>
1374
+ <div className="flex flex-wrap items-center gap-2">
1375
+ <FormControl>
1376
+ <Input
1377
+ data-testid="ai-max-tokens-input"
1378
+ type="number"
1379
+ min={1}
1380
+ disabled={!maxTokensEnabled}
1381
+ className="w-20 h-7"
1382
+ value={field.value ?? (maxTokensEnabled ? "" : 32768)}
1383
+ onChange={(e) => {
1384
+ const n = Number.parseInt(e.target.value, 10);
1385
+ field.onChange(Number.isFinite(n) && n > 0 ? n : null);
1386
+ }}
1387
+ />
1388
+ </FormControl>
1389
+ <FormItem className="flex-row items-center gap-1.5">
1390
+ <FormControl>
1391
+ <Checkbox
1392
+ data-testid="ai-max-tokens-checkbox"
1393
+ checked={maxTokensEnabled}
1394
+ onCheckedChange={(checked) => {
1395
+ const isChecked = checked === true;
1396
+ setMaxTokensEnabled(isChecked);
1397
+ // null signals delete to the server; cast because
1398
+ // UserConfig (OpenAPI-derived) types max_tokens as
1399
+ // `number | undefined`, but zod accepts `null`.
1400
+ const next = (
1401
+ isChecked ? (field.value ?? 32768) : null
1402
+ ) as number | undefined;
1403
+ // shouldDirty: true forces RHF to keep this in
1404
+ // dirtyFields even when `next` happens to equal the
1405
+ // form's defaultValue (e.g. untick → tick when disk
1406
+ // started with 32768). Otherwise getDirtyValues
1407
+ // would skip it and the save body would be empty.
1408
+ form.setValue("ai.max_tokens", next, {
1409
+ shouldDirty: true,
1410
+ shouldTouch: true,
1411
+ });
1412
+ onSubmit(form.getValues());
1413
+ }}
1414
+ />
1415
+ </FormControl>
1416
+ <FormLabel className="font-normal">Override</FormLabel>
1417
+ </FormItem>
1418
+ </div>
1419
+ </FormItem>
1420
+ </div>
1421
+ );
1422
+ }}
1423
+ />
1424
+
1447
1425
  <OverriddenFormField
1448
1426
  control={form.control}
1449
1427
  name="ai.rules"
1450
1428
  render={({ field, override }) => (
1451
- <div className="flex flex-col">
1429
+ <div className="settings-field flex flex-col">
1452
1430
  <FormItem>
1453
- <FormLabel>Custom Rules</FormLabel>
1431
+ <FormLabel>Custom rules</FormLabel>
1432
+ <FormDescription>
1433
+ Custom rules to include in all AI completion prompts.
1434
+ </FormDescription>
1454
1435
  <FormControl>
1455
1436
  <Textarea
1456
1437
  data-testid="ai-rules-input"
@@ -1464,8 +1445,28 @@ export const AiAssistConfig: React.FC<AiConfigProps> = ({
1464
1445
  <FormMessage />
1465
1446
  <IsOverridden override={override} />
1466
1447
  </FormItem>
1448
+ </div>
1449
+ )}
1450
+ />
1451
+ <FormField
1452
+ control={form.control}
1453
+ name="ai.inline_tooltip"
1454
+ render={({ field }) => (
1455
+ <div className="settings-field flex flex-col gap-y-1">
1456
+ <FormItem className={formItemClasses}>
1457
+ <FormLabel className="font-normal">
1458
+ Show “Edit with AI” on text selection
1459
+ </FormLabel>
1460
+ <FormControl>
1461
+ <Checkbox
1462
+ data-testid="inline-ai-checkbox"
1463
+ checked={field.value === true}
1464
+ onCheckedChange={field.onChange}
1465
+ />
1466
+ </FormControl>
1467
+ </FormItem>
1467
1468
  <FormDescription>
1468
- Custom rules to include in all AI completion prompts.
1469
+ Enable "Edit with AI" tooltip when selecting code.
1469
1470
  </FormDescription>
1470
1471
  </div>
1471
1472
  )}
@@ -1658,7 +1659,7 @@ export const AiModelDisplayConfig: React.FC<AiConfigProps> = ({
1658
1659
 
1659
1660
  <div className="bg-background">
1660
1661
  <Tree
1661
- aria-label="AI Models by Provider"
1662
+ aria-label="AI models by provider"
1662
1663
  className="flex-1 overflow-auto outline-hidden focus-visible:outline-hidden"
1663
1664
  selectionMode="none"
1664
1665
  >
@@ -1770,7 +1771,7 @@ export const AddModelForm: React.FC<{
1770
1771
  {customProviderNames.length > 0 && (
1771
1772
  <>
1772
1773
  <p className="px-2 py-1 text-xs text-muted-secondary font-medium">
1773
- Custom Providers
1774
+ Custom providers
1774
1775
  </p>
1775
1776
  {customProviderNames.map((p) => (
1776
1777
  <SelectItem key={p} value={p}>
@@ -1782,7 +1783,7 @@ export const AddModelForm: React.FC<{
1782
1783
  ))}
1783
1784
  {knownProviders.length > 0 && (
1784
1785
  <p className="px-2 py-1 text-xs text-muted-secondary font-medium mt-1">
1785
- Built-in Providers
1786
+ Built-in providers
1786
1787
  </p>
1787
1788
  )}
1788
1789
  </>
@@ -1881,7 +1882,7 @@ export const AddModelForm: React.FC<{
1881
1882
  <AddButton
1882
1883
  isFormOpen={isFormOpen}
1883
1884
  setIsFormOpen={setIsFormOpen}
1884
- label="Add Model"
1885
+ label="Add model"
1885
1886
  className="pl-2"
1886
1887
  />
1887
1888
  {modelAdded && (
@@ -1935,9 +1936,9 @@ const AiEnabledConfig: React.FC<AiConfigProps> = ({ form }) => {
1935
1936
  control={form.control}
1936
1937
  name="ai.enabled"
1937
1938
  render={({ field, override }) => (
1938
- <div className="flex flex-col gap-y-1">
1939
+ <div className="settings-field flex flex-col gap-y-1">
1939
1940
  <FormItem className={formItemClasses}>
1940
- <FormLabel className="font-normal">Enable AI features</FormLabel>
1941
+ <FormLabel className="font-normal">Show AI features</FormLabel>
1941
1942
  <FormControl>
1942
1943
  <Checkbox
1943
1944
  data-testid="ai-enabled-checkbox"
@@ -1951,8 +1952,8 @@ const AiEnabledConfig: React.FC<AiConfigProps> = ({ form }) => {
1951
1952
  <IsOverridden override={override} />
1952
1953
  </FormItem>
1953
1954
  <FormDescription>
1954
- When disabled, AI actions and panels are hidden from the marimo
1955
- UI.
1955
+ When disabled, various AI-related features are hidden from the
1956
+ marimo UI.
1956
1957
  </FormDescription>
1957
1958
  </div>
1958
1959
  )}
@@ -1986,32 +1987,32 @@ export const AiConfig: React.FC<AiConfigProps> = ({
1986
1987
  onValueChange={(value) => setActiveTab(value as AiSettingsSubTab)}
1987
1988
  className="flex-1"
1988
1989
  >
1989
- <TabsList className="mb-2">
1990
- <TabsTrigger value="ai-features">AI Features</TabsTrigger>
1990
+ <TabsList className="mb-2 max-w-full justify-start overflow-x-auto">
1991
+ <TabsTrigger value="ai-features">AI features</TabsTrigger>
1991
1992
  {aiEnabled !== false && (
1992
1993
  <>
1993
- <TabsTrigger value="ai-providers">AI Providers</TabsTrigger>
1994
- <TabsTrigger value="ai-models">AI Models</TabsTrigger>
1994
+ <TabsTrigger value="ai-providers">AI providers</TabsTrigger>
1995
+ <TabsTrigger value="ai-models">AI models</TabsTrigger>
1995
1996
  {!wasm && <TabsTrigger value="mcp">MCP</TabsTrigger>}
1996
1997
  </>
1997
1998
  )}
1998
1999
  </TabsList>
2000
+ {aiEnabled !== false && <PairWithAgentBanner className="mt-2 mb-6" />}
1999
2001
 
2000
- <TabsContent value="ai-features">
2002
+ <TabsContent value="ai-features" className="space-y-6">
2003
+ {aiEnabled !== false && (
2004
+ <AiAssistConfig form={form} config={config} onSubmit={onSubmit} />
2005
+ )}
2001
2006
  <AiCodeCompletionConfig
2002
2007
  form={form}
2003
2008
  config={config}
2004
2009
  onSubmit={onSubmit}
2005
2010
  />
2006
2011
  <AiEnabledConfig form={form} config={config} onSubmit={onSubmit} />
2007
- {aiEnabled !== false && (
2008
- <AiAssistConfig form={form} config={config} onSubmit={onSubmit} />
2009
- )}
2010
2012
  </TabsContent>
2011
2013
  {aiEnabled !== false && (
2012
2014
  <>
2013
2015
  <TabsContent value="ai-providers">
2014
- <PairWithAgentBanner className="mb-4" />
2015
2016
  <AiProvidersConfig
2016
2017
  form={form}
2017
2018
  config={config}
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { useAtom } from "jotai";
4
4
  import { SettingsIcon } from "lucide-react";
5
- import { VisuallyHidden } from "react-aria";
6
5
  import { AppConfigForm } from "@/components/app-config/app-config-form";
7
6
  import {
8
7
  Popover,
@@ -51,10 +50,12 @@ export const ConfigButton: React.FC<Props> = ({
51
50
  );
52
51
 
53
52
  const userSettingsDialog = (
54
- <DialogContent className="w-[90vw] h-[90vh] overflow-hidden sm:max-w-5xl top-[5vh] p-0">
55
- <VisuallyHidden>
56
- <DialogTitle>User settings</DialogTitle>
57
- </VisuallyHidden>
53
+ <DialogContent className="flex flex-col gap-0 w-[94vw] h-[90vh] overflow-hidden sm:max-w-5xl top-[5vh] p-0 font-prose">
54
+ <div className="border-b px-6 py-4 pr-12">
55
+ <DialogTitle className="text-base font-semibold">
56
+ User settings
57
+ </DialogTitle>
58
+ </div>
58
59
  <UserConfigForm />
59
60
  </DialogContent>
60
61
  );
@@ -4,7 +4,8 @@ import type { HTMLProps, PropsWithChildren } from "react";
4
4
  import type { SqlOutputType } from "@/core/config/config-schema";
5
5
  import { cn } from "@/utils/cn";
6
6
 
7
- export const formItemClasses = "flex flex-row items-center space-x-1 space-y-0";
7
+ export const formItemClasses =
8
+ "settings-row flex flex-row items-center space-x-1 space-y-0";
8
9
 
9
10
  export const SettingTitle: React.FC<PropsWithChildren> = ({ children }) => {
10
11
  return (
@@ -14,21 +15,18 @@ export const SettingTitle: React.FC<PropsWithChildren> = ({ children }) => {
14
15
  );
15
16
  };
16
17
 
17
- export const SettingSubtitle: React.FC<HTMLProps<HTMLDivElement>> = ({
18
+ export const SettingSubtitle: React.FC<HTMLProps<HTMLHeadingElement>> = ({
18
19
  children,
19
20
  className,
20
21
  ...props
21
22
  }) => {
22
23
  return (
23
- <div
24
+ <h3
24
25
  {...props}
25
- className={cn(
26
- "text-base font-semibold underline-offset-2 text-accent-foreground uppercase tracking-wide",
27
- className,
28
- )}
26
+ className={cn("text-base font-semibold text-foreground", className)}
29
27
  >
30
28
  {children}
31
- </div>
29
+ </h3>
32
30
  );
33
31
  };
34
32
 
@@ -43,7 +41,7 @@ export const SettingGroup: React.FC<{
43
41
  children: React.ReactNode;
44
42
  }> = ({ title, children }) => {
45
43
  return (
46
- <div className="flex flex-col gap-4 pb-4">
44
+ <div className="settings-group flex flex-col gap-4">
47
45
  <SettingSubtitle>{title}</SettingSubtitle>
48
46
  {children}
49
47
  </div>