@marimo-team/islands 0.15.1-dev20 → 0.15.1-dev23
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/dist/{ConnectedDataExplorerComponent-T4kWx74k.js → ConnectedDataExplorerComponent-D0XqshlU.js} +2 -2
- package/dist/{ImageComparisonComponent-DGrNyuT6.js → ImageComparisonComponent-CZsQglqB.js} +1 -1
- package/dist/{_baseUniq-DDI83xpx.js → _baseUniq-Ckiyck6A.js} +1 -1
- package/dist/{any-language-editor-OdwxdANJ.js → any-language-editor-BQyFbRCa.js} +1 -1
- package/dist/{architectureDiagram-KFL7JDKH-Bz4LLaRM.js → architectureDiagram-KFL7JDKH-Bf0uTVKX.js} +4 -4
- package/dist/{blockDiagram-ZYB65J3Q-Bn75dKCF.js → blockDiagram-ZYB65J3Q-COqr4VwW.js} +5 -5
- package/dist/{c4Diagram-AAMF2YG6-OUTC8UCE.js → c4Diagram-AAMF2YG6-BuIrt4mL.js} +2 -2
- package/dist/{channel-D2Pk_7pD.js → channel-DtQ-9KOG.js} +1 -1
- package/dist/{chunk-ANTBXLJU-BOcGK9IQ.js → chunk-ANTBXLJU-qUpcxtn9.js} +1 -1
- package/dist/{chunk-FHKO5MBM-Itt815NO.js → chunk-FHKO5MBM-KUh3TLMU.js} +1 -1
- package/dist/{chunk-GLLZNHP4-BlugZIAr.js → chunk-GLLZNHP4-BG2l4nQz.js} +1 -1
- package/dist/{chunk-JBRWN2VN-CMPZS_JC.js → chunk-JBRWN2VN-DcOjZ6W6.js} +4 -4
- package/dist/{chunk-LXBSTHXV-B9m0o3Dw.js → chunk-LXBSTHXV-1wtuuDPb.js} +3 -3
- package/dist/{chunk-NRVI72HA-CUWnO70H.js → chunk-NRVI72HA-BVVW7BOP.js} +1 -1
- package/dist/{chunk-OMD6QJNC-C0G1Ybvw.js → chunk-OMD6QJNC-BOOCPvHU.js} +1 -1
- package/dist/{chunk-WVR4S24B-CfqNG6BS.js → chunk-WVR4S24B-CWMArkgK.js} +1 -1
- package/dist/{classDiagram-v2-QTMF73CY-Du3X9bsO.js → classDiagram-3BZAVTQC-BjnqEj2F.js} +2 -2
- package/dist/{classDiagram-3BZAVTQC-Du3X9bsO.js → classDiagram-v2-QTMF73CY-BjnqEj2F.js} +2 -2
- package/dist/{clone-CHk_BpQ7.js → clone-DD_V4yHs.js} +1 -1
- package/dist/{dagre-2BBEFEWP-C21lJehF.js → dagre-2BBEFEWP-DyqlYi2j.js} +6 -6
- package/dist/{data-grid-overlay-editor-DQAvWUme.js → data-grid-overlay-editor-CcVaBCuH.js} +2 -2
- package/dist/{diagram-4IRLE6MV-DiqIOlJy.js → diagram-4IRLE6MV-CreyFvcp.js} +5 -5
- package/dist/{diagram-GUPCWM2R-QNnoayaR.js → diagram-GUPCWM2R-AveBH1M-.js} +3 -3
- package/dist/{diagram-RP2FKANI-D0mh9jtM.js → diagram-RP2FKANI-CHEhrmw1.js} +3 -3
- package/dist/{erDiagram-HZWUO2LU-CHNzznx-.js → erDiagram-HZWUO2LU-Bo1td0a2.js} +4 -4
- package/dist/{flowDiagram-THRYKUMA-_FZ6mTfz.js → flowDiagram-THRYKUMA-DV0yuxL7.js} +5 -5
- package/dist/{ganttDiagram-WV7ZQ7D5-CaYCAAh_.js → ganttDiagram-WV7ZQ7D5-DFlgXP8C.js} +4 -4
- package/dist/{gitGraphDiagram-OJR772UL-D_G318ZT.js → gitGraphDiagram-OJR772UL-DfdoeHs8.js} +4 -4
- package/dist/{glide-data-editor-DwzWASdN.js → glide-data-editor-AoDOWQjZ.js} +3 -3
- package/dist/{graph-Dkiu8g_M.js → graph-CreegasO.js} +3 -3
- package/dist/{index-Dc1_AAWe.js → index-BA-ePze6.js} +3 -3
- package/dist/{index-r5Jrqg8i.js → index-BrVUS1nc.js} +1 -1
- package/dist/{index-OIcVgWd2.js → index-C-0pD2W0.js} +1 -1
- package/dist/{index-CxgXuB1J.js → index-CkZ7D2Ry.js} +1 -1
- package/dist/{index-w90izJvc.js → index-DvKEL24D.js} +1 -1
- package/dist/{infoDiagram-6WOFNB3A-DtTO4vv-.js → infoDiagram-6WOFNB3A-Bzxw7XWX.js} +2 -2
- package/dist/{journeyDiagram-FFXJYRFH-Bc6OYvB0.js → journeyDiagram-FFXJYRFH-BzAcL7-t.js} +3 -3
- package/dist/{kanban-definition-KOZQBZVT-BJxHSdqr.js → kanban-definition-KOZQBZVT-DNwe7TQc.js} +2 -2
- package/dist/{layout-b98w0jb2.js → layout-Bhbgjfgd.js} +4 -4
- package/dist/{linear-DzMFB4Mu.js → linear-CMGuqFex.js} +1 -1
- package/dist/{main-3-uFVaKp.js → main-DqZNwn-r.js} +22 -21
- package/dist/main.js +1 -1
- package/dist/{mermaid-DnVrAn0b.js → mermaid-DYU5EKQx.js} +29 -29
- package/dist/{min-C8XS9LSa.js → min-cWlAN3aL.js} +2 -2
- package/dist/{mindmap-definition-LNHGMQRG-DNwr3bGQ.js → mindmap-definition-LNHGMQRG-SqmjJMOX.js} +2 -2
- package/dist/{number-overlay-editor-B97boK1K.js → number-overlay-editor-B_N8oIlq.js} +2 -2
- package/dist/{pieDiagram-DBDJKBY4-BL1ymVxH.js → pieDiagram-DBDJKBY4-CMBU4--E.js} +3 -3
- package/dist/{quadrantDiagram-YPSRARAO-DhHySUbQ.js → quadrantDiagram-YPSRARAO-C-eVThNr.js} +2 -2
- package/dist/{react-plotly-CTH_zZDu.js → react-plotly-BxPqO_XY.js} +1 -1
- package/dist/{requirementDiagram-EGVEC5DT-O8owc5D5.js → requirementDiagram-EGVEC5DT-B-JMjvqi.js} +3 -3
- package/dist/{sankeyDiagram-HRAUVNP4-Db6_uxKW.js → sankeyDiagram-HRAUVNP4-Dp9GioK7.js} +1 -1
- package/dist/{sequenceDiagram-WFGC7UMF-Bnf8-z29.js → sequenceDiagram-WFGC7UMF-DSah_xWf.js} +3 -3
- package/dist/{slides-component-DCeqnno4.js → slides-component-CqHjN5_K.js} +1 -1
- package/dist/{stateDiagram-UUKSUZ4H-Dd4BaplA.js → stateDiagram-UUKSUZ4H-3XFJ-Sg5.js} +4 -4
- package/dist/{stateDiagram-v2-EYPG3UTE-B2UNCxuY.js → stateDiagram-v2-EYPG3UTE-viJ4Y_Zw.js} +2 -2
- package/dist/{time-CACu73BN.js → time-DqeWEGuD.js} +2 -2
- package/dist/{timeline-definition-3HZDQTIS-CeJCv_tk.js → timeline-definition-3HZDQTIS-CMJ28VYj.js} +1 -1
- package/dist/{treemap-75Q7IDZK-D1YD8J0D.js → treemap-75Q7IDZK-B_H3ereo.js} +5 -5
- package/dist/{vega-component-B0qW_XiJ.js → vega-component-C5l0rC2E.js} +2 -2
- package/dist/{xychartDiagram-FDP5SA34-BCWhiZdq.js → xychartDiagram-FDP5SA34-CLpc6DE1.js} +2 -2
- package/package.json +19 -19
- package/src/components/ai/ai-model-dropdown.tsx +5 -34
- package/src/components/ai/display-helpers.tsx +32 -0
- package/src/components/app-config/ai-config.tsx +265 -9
- package/src/components/app-config/app-config-button.tsx +1 -1
- package/src/components/app-config/app-config-form.tsx +17 -6
- package/src/components/app-config/user-config-form.tsx +5 -1
- package/src/core/ai/__tests__/model-registry.test.ts +25 -1
- package/src/core/ai/model-registry.ts +36 -4
- package/src/hooks/useDebounce.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marimo-team/islands",
|
|
3
|
-
"version": "0.15.1-
|
|
3
|
+
"version": "0.15.1-dev23",
|
|
4
4
|
"main": "dist/main.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -60,29 +60,29 @@
|
|
|
60
60
|
"@open-rpc/client-js": "^1.8.1",
|
|
61
61
|
"@paralleldrive/cuid2": "^2.2.2",
|
|
62
62
|
"@radix-ui/colors": "^3.0.0",
|
|
63
|
-
"@radix-ui/primitive": "~1.1.
|
|
64
|
-
"@radix-ui/react-accordion": "~1.2.
|
|
65
|
-
"@radix-ui/react-alert-dialog": "~1.1.
|
|
66
|
-
"@radix-ui/react-checkbox": "~1.3.
|
|
63
|
+
"@radix-ui/primitive": "~1.1.3",
|
|
64
|
+
"@radix-ui/react-accordion": "~1.2.12",
|
|
65
|
+
"@radix-ui/react-alert-dialog": "~1.1.15",
|
|
66
|
+
"@radix-ui/react-checkbox": "~1.3.3",
|
|
67
67
|
"@radix-ui/react-compose-refs": "~1.1.2",
|
|
68
|
-
"@radix-ui/react-context-menu": "~2.2.
|
|
69
|
-
"@radix-ui/react-dialog": "~1.1.
|
|
70
|
-
"@radix-ui/react-dropdown-menu": "~2.1.
|
|
68
|
+
"@radix-ui/react-context-menu": "~2.2.16",
|
|
69
|
+
"@radix-ui/react-dialog": "~1.1.15",
|
|
70
|
+
"@radix-ui/react-dropdown-menu": "~2.1.16",
|
|
71
71
|
"@radix-ui/react-icons": "~1.3.2",
|
|
72
72
|
"@radix-ui/react-label": "~2.1.7",
|
|
73
|
-
"@radix-ui/react-navigation-menu": "~1.2.
|
|
74
|
-
"@radix-ui/react-popover": "~1.1.
|
|
73
|
+
"@radix-ui/react-navigation-menu": "~1.2.14",
|
|
74
|
+
"@radix-ui/react-popover": "~1.1.15",
|
|
75
75
|
"@radix-ui/react-progress": "~1.1.7",
|
|
76
|
-
"@radix-ui/react-radio-group": "~1.3.
|
|
77
|
-
"@radix-ui/react-scroll-area": "^1.2.
|
|
78
|
-
"@radix-ui/react-select": "~2.2.
|
|
79
|
-
"@radix-ui/react-slider": "~1.3.
|
|
76
|
+
"@radix-ui/react-radio-group": "~1.3.8",
|
|
77
|
+
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
78
|
+
"@radix-ui/react-select": "~2.2.6",
|
|
79
|
+
"@radix-ui/react-slider": "~1.3.6",
|
|
80
80
|
"@radix-ui/react-slot": "~1.2.3",
|
|
81
|
-
"@radix-ui/react-switch": "~1.2.
|
|
82
|
-
"@radix-ui/react-tabs": "~1.1.
|
|
83
|
-
"@radix-ui/react-toast": "~1.2.
|
|
84
|
-
"@radix-ui/react-toggle": "~1.1.
|
|
85
|
-
"@radix-ui/react-tooltip": "~1.2.
|
|
81
|
+
"@radix-ui/react-switch": "~1.2.6",
|
|
82
|
+
"@radix-ui/react-tabs": "~1.1.13",
|
|
83
|
+
"@radix-ui/react-toast": "~1.2.15",
|
|
84
|
+
"@radix-ui/react-toggle": "~1.1.10",
|
|
85
|
+
"@radix-ui/react-tooltip": "~1.2.8",
|
|
86
86
|
"@radix-ui/react-use-callback-ref": "~1.1.1",
|
|
87
87
|
"@radix-ui/react-use-controllable-state": "~1.2.2",
|
|
88
88
|
"@replit/codemirror-vim": "^6.3.0",
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "../ui/dropdown-menu";
|
|
32
32
|
import { Tooltip } from "../ui/tooltip";
|
|
33
33
|
import { AiProviderIcon } from "./ai-provider-icon";
|
|
34
|
+
import { getCurrentRoleTooltip, getTagColour } from "./display-helpers";
|
|
34
35
|
|
|
35
36
|
interface AIModelDropdownProps {
|
|
36
37
|
value?: string;
|
|
@@ -104,7 +105,7 @@ export const AIModelDropdown = ({
|
|
|
104
105
|
</div>
|
|
105
106
|
|
|
106
107
|
<div className="ml-auto flex gap-1">
|
|
107
|
-
<Tooltip content={
|
|
108
|
+
<Tooltip content={getCurrentRoleTooltip(role)}>
|
|
108
109
|
<span
|
|
109
110
|
key={role}
|
|
110
111
|
className={`text-xs px-1.5 py-0.5 rounded font-medium ${getTagColour(role)}`}
|
|
@@ -248,8 +249,7 @@ const ProviderDropdownContent = ({
|
|
|
248
249
|
</>
|
|
249
250
|
)}
|
|
250
251
|
{models.map((model) => {
|
|
251
|
-
const qualifiedModelId =
|
|
252
|
-
`${provider}/${model.model}` as QualifiedModelId;
|
|
252
|
+
const qualifiedModelId: QualifiedModelId = `${provider}/${model.model}`;
|
|
253
253
|
return (
|
|
254
254
|
<DropdownMenuSub key={qualifiedModelId}>
|
|
255
255
|
<DropdownMenuSubTrigger showChevron={false} className="py-2">
|
|
@@ -309,7 +309,7 @@ const AiModelDropdownItem = ({
|
|
|
309
309
|
);
|
|
310
310
|
};
|
|
311
311
|
|
|
312
|
-
const AiModelInfoDisplay = ({
|
|
312
|
+
export const AiModelInfoDisplay = ({
|
|
313
313
|
model,
|
|
314
314
|
provider,
|
|
315
315
|
}: {
|
|
@@ -339,7 +339,7 @@ const AiModelInfoDisplay = ({
|
|
|
339
339
|
<span
|
|
340
340
|
key={role}
|
|
341
341
|
className={`px-2 py-1 text-xs rounded-md font-medium ${getTagColour(role)}`}
|
|
342
|
-
title={
|
|
342
|
+
title={getCurrentRoleTooltip(role)}
|
|
343
343
|
>
|
|
344
344
|
{role}
|
|
345
345
|
</span>
|
|
@@ -374,32 +374,3 @@ function getProviderLabel(provider: ProviderId): string {
|
|
|
374
374
|
}
|
|
375
375
|
return capitalize(provider);
|
|
376
376
|
}
|
|
377
|
-
|
|
378
|
-
function getTagColour(role: Role | "thinking"): string {
|
|
379
|
-
switch (role) {
|
|
380
|
-
case "chat":
|
|
381
|
-
return "bg-[var(--purple-3)] text-[var(--purple-11)]";
|
|
382
|
-
case "autocomplete":
|
|
383
|
-
return "bg-[var(--green-3)] text-[var(--green-11)]";
|
|
384
|
-
case "edit":
|
|
385
|
-
return "bg-[var(--blue-3)] text-[var(--blue-11)]";
|
|
386
|
-
case "thinking":
|
|
387
|
-
return "bg-[var(--purple-4)] text-[var(--purple-12)]";
|
|
388
|
-
}
|
|
389
|
-
return "bg-[var(--mauve-3)] text-[var(--mauve-11)]";
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function getTagTooltip(role: Role): string {
|
|
393
|
-
switch (role) {
|
|
394
|
-
case "chat":
|
|
395
|
-
return "Current model used for chat conversations";
|
|
396
|
-
case "autocomplete":
|
|
397
|
-
return "Current model used for autocomplete autocomplete";
|
|
398
|
-
case "edit":
|
|
399
|
-
return "Current model used for code edits";
|
|
400
|
-
case "rerank":
|
|
401
|
-
return "Current model used for reranking completions";
|
|
402
|
-
case "embed":
|
|
403
|
-
return "Current model used for embedding";
|
|
404
|
-
}
|
|
405
|
-
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { Role } from "@marimo-team/llm-info";
|
|
4
|
+
|
|
5
|
+
export function getTagColour(role: Role | "thinking"): string {
|
|
6
|
+
switch (role) {
|
|
7
|
+
case "chat":
|
|
8
|
+
return "bg-[var(--purple-3)] text-[var(--purple-11)]";
|
|
9
|
+
case "autocomplete":
|
|
10
|
+
return "bg-[var(--green-3)] text-[var(--green-11)]";
|
|
11
|
+
case "edit":
|
|
12
|
+
return "bg-[var(--blue-3)] text-[var(--blue-11)]";
|
|
13
|
+
case "thinking":
|
|
14
|
+
return "bg-[var(--purple-4)] text-[var(--purple-12)]";
|
|
15
|
+
}
|
|
16
|
+
return "bg-[var(--mauve-3)] text-[var(--mauve-11)]";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function getCurrentRoleTooltip(role: Role): string {
|
|
20
|
+
switch (role) {
|
|
21
|
+
case "chat":
|
|
22
|
+
return "Current model used for chat conversations";
|
|
23
|
+
case "autocomplete":
|
|
24
|
+
return "Current model used for autocomplete autocomplete";
|
|
25
|
+
case "edit":
|
|
26
|
+
return "Current model used for code edits";
|
|
27
|
+
case "rerank":
|
|
28
|
+
return "Current model used for reranking completions";
|
|
29
|
+
case "embed":
|
|
30
|
+
return "Current model used for embedding";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import { InfoIcon } from "lucide-react";
|
|
4
|
-
import React from "react";
|
|
3
|
+
import { BrainIcon, ChevronRightIcon, InfoIcon } from "lucide-react";
|
|
4
|
+
import React, { useMemo } from "react";
|
|
5
|
+
import {
|
|
6
|
+
Button as AriaButton,
|
|
7
|
+
Tree,
|
|
8
|
+
TreeItem,
|
|
9
|
+
TreeItemContent,
|
|
10
|
+
} from "react-aria-components";
|
|
5
11
|
import type { FieldPath, UseFormReturn } from "react-hook-form";
|
|
12
|
+
import { useWatch } from "react-hook-form";
|
|
13
|
+
import useEvent from "react-use-event-hook";
|
|
6
14
|
import {
|
|
7
15
|
FormControl,
|
|
8
16
|
FormDescription,
|
|
@@ -16,24 +24,34 @@ import { Input } from "@/components/ui/input";
|
|
|
16
24
|
import { Kbd } from "@/components/ui/kbd";
|
|
17
25
|
import { NativeSelect } from "@/components/ui/native-select";
|
|
18
26
|
import { Textarea } from "@/components/ui/textarea";
|
|
19
|
-
import
|
|
27
|
+
import {
|
|
28
|
+
AiModelId,
|
|
29
|
+
type ProviderId,
|
|
30
|
+
type QualifiedModelId,
|
|
31
|
+
} from "@/core/ai/ids/ids";
|
|
32
|
+
import { type AiModel, AiModelRegistry } from "@/core/ai/model-registry";
|
|
20
33
|
import { CopilotConfig } from "@/core/codemirror/copilot/copilot-config";
|
|
21
34
|
import { DEFAULT_AI_MODEL, type UserConfig } from "@/core/config/config-schema";
|
|
22
35
|
import { isWasm } from "@/core/wasm/utils";
|
|
36
|
+
import { cn } from "@/utils/cn";
|
|
23
37
|
import { Events } from "@/utils/events";
|
|
38
|
+
import { Strings } from "@/utils/strings";
|
|
24
39
|
import { AIModelDropdown } from "../ai/ai-model-dropdown";
|
|
25
40
|
import {
|
|
26
41
|
AiProviderIcon,
|
|
27
42
|
type AiProviderIconProps,
|
|
28
43
|
} from "../ai/ai-provider-icon";
|
|
44
|
+
import { getTagColour } from "../ai/display-helpers";
|
|
29
45
|
import {
|
|
30
46
|
Accordion,
|
|
31
47
|
AccordionContent,
|
|
32
48
|
AccordionItem,
|
|
33
49
|
AccordionTrigger,
|
|
34
50
|
} from "../ui/accordion";
|
|
51
|
+
import { Checkbox } from "../ui/checkbox";
|
|
35
52
|
import { DropdownMenuSeparator } from "../ui/dropdown-menu";
|
|
36
53
|
import { ExternalLink } from "../ui/links";
|
|
54
|
+
import { Switch } from "../ui/switch";
|
|
37
55
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
|
38
56
|
import { Tooltip } from "../ui/tooltip";
|
|
39
57
|
import { SettingSubtitle } from "./common";
|
|
@@ -46,7 +64,7 @@ const formItemClasses = "flex flex-row items-center space-x-1 space-y-0";
|
|
|
46
64
|
interface AiConfigProps {
|
|
47
65
|
form: UseFormReturn<UserConfig>;
|
|
48
66
|
config: UserConfig;
|
|
49
|
-
onSubmit: (values: UserConfig) =>
|
|
67
|
+
onSubmit: (values: UserConfig) => void;
|
|
50
68
|
}
|
|
51
69
|
|
|
52
70
|
interface AiProviderTitleProps {
|
|
@@ -189,7 +207,7 @@ interface ModelSelectorProps {
|
|
|
189
207
|
description?: React.ReactNode;
|
|
190
208
|
disabled?: boolean;
|
|
191
209
|
label: string;
|
|
192
|
-
onSubmit: (values: UserConfig) =>
|
|
210
|
+
onSubmit: (values: UserConfig) => void;
|
|
193
211
|
}
|
|
194
212
|
|
|
195
213
|
export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|
@@ -213,6 +231,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|
|
213
231
|
|
|
214
232
|
const selectModel = (modelId: QualifiedModelId) => {
|
|
215
233
|
field.onChange(modelId);
|
|
234
|
+
// Usually not needed, but a hack to force form values to be updated
|
|
216
235
|
onSubmit(form.getValues());
|
|
217
236
|
};
|
|
218
237
|
|
|
@@ -340,7 +359,7 @@ const renderCopilotProvider = ({
|
|
|
340
359
|
}: {
|
|
341
360
|
form: UseFormReturn<UserConfig>;
|
|
342
361
|
config: UserConfig;
|
|
343
|
-
onSubmit: (values: UserConfig) =>
|
|
362
|
+
onSubmit: (values: UserConfig) => void;
|
|
344
363
|
}) => {
|
|
345
364
|
const copilot = form.getValues("completion.copilot");
|
|
346
365
|
if (copilot === false) {
|
|
@@ -388,8 +407,88 @@ const renderCopilotProvider = ({
|
|
|
388
407
|
}
|
|
389
408
|
};
|
|
390
409
|
|
|
391
|
-
const SettingGroup = ({
|
|
392
|
-
|
|
410
|
+
const SettingGroup = ({
|
|
411
|
+
children,
|
|
412
|
+
className,
|
|
413
|
+
}: {
|
|
414
|
+
children: React.ReactNode;
|
|
415
|
+
className?: string;
|
|
416
|
+
}) => {
|
|
417
|
+
return (
|
|
418
|
+
<div className={cn("flex flex-col gap-4 pb-4", className)}>{children}</div>
|
|
419
|
+
);
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
interface ModelListItemProps {
|
|
423
|
+
qualifiedId: QualifiedModelId;
|
|
424
|
+
model: AiModel;
|
|
425
|
+
isEnabled: boolean;
|
|
426
|
+
onToggle: (modelId: QualifiedModelId) => void;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const ModelListItem: React.FC<ModelListItemProps> = ({
|
|
430
|
+
qualifiedId,
|
|
431
|
+
model,
|
|
432
|
+
isEnabled,
|
|
433
|
+
onToggle,
|
|
434
|
+
}) => {
|
|
435
|
+
const handleToggle = () => {
|
|
436
|
+
onToggle(qualifiedId);
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
return (
|
|
440
|
+
<TreeItem
|
|
441
|
+
id={qualifiedId}
|
|
442
|
+
textValue={model.name}
|
|
443
|
+
className="pl-6 outline-none data-focused:bg-muted/50 hover:bg-muted/50"
|
|
444
|
+
onAction={handleToggle}
|
|
445
|
+
>
|
|
446
|
+
<TreeItemContent>
|
|
447
|
+
<div className="flex items-center justify-between px-4 py-3 border-b last:border-b-0 cursor-pointer outline-none">
|
|
448
|
+
<ModelInfoCard model={model} qualifiedId={qualifiedId} />
|
|
449
|
+
<Switch checked={isEnabled} onClick={handleToggle} size="sm" />
|
|
450
|
+
</div>
|
|
451
|
+
</TreeItemContent>
|
|
452
|
+
</TreeItem>
|
|
453
|
+
);
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const ModelInfoCard = ({
|
|
457
|
+
model,
|
|
458
|
+
qualifiedId,
|
|
459
|
+
}: {
|
|
460
|
+
model: AiModel;
|
|
461
|
+
qualifiedId: QualifiedModelId;
|
|
462
|
+
}) => {
|
|
463
|
+
return (
|
|
464
|
+
<div className="flex items-center gap-3 flex-1">
|
|
465
|
+
<div className="flex flex-col flex-1">
|
|
466
|
+
<div className="flex items-center gap-2">
|
|
467
|
+
<h3 className="font-medium">{model.name}</h3>
|
|
468
|
+
</div>
|
|
469
|
+
<span className="text-xs text-muted-foreground font-mono">
|
|
470
|
+
{qualifiedId}
|
|
471
|
+
</span>
|
|
472
|
+
{model.description && !model.custom && (
|
|
473
|
+
<p className="text-sm text-muted-secondary mt-1 line-clamp-2">
|
|
474
|
+
{model.description}
|
|
475
|
+
</p>
|
|
476
|
+
)}
|
|
477
|
+
|
|
478
|
+
{model.thinking && (
|
|
479
|
+
<div
|
|
480
|
+
className={cn(
|
|
481
|
+
"flex items-center gap-1 rounded px-1 py-0.5 w-fit mt-1.5",
|
|
482
|
+
getTagColour("thinking"),
|
|
483
|
+
)}
|
|
484
|
+
>
|
|
485
|
+
<BrainIcon className="h-3 w-3" />
|
|
486
|
+
<span className="text-xs font-medium">Reasoning</span>
|
|
487
|
+
</div>
|
|
488
|
+
)}
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
);
|
|
393
492
|
};
|
|
394
493
|
|
|
395
494
|
export const AiCodeCompletionConfig: React.FC<AiConfigProps> = ({
|
|
@@ -827,16 +926,170 @@ export const AiAssistConfig: React.FC<AiConfigProps> = ({
|
|
|
827
926
|
);
|
|
828
927
|
};
|
|
829
928
|
|
|
929
|
+
interface ProviderTreeItemProps {
|
|
930
|
+
providerId: ProviderId;
|
|
931
|
+
models: AiModel[];
|
|
932
|
+
enabledModels: Set<QualifiedModelId>;
|
|
933
|
+
onToggleModel: (modelId: QualifiedModelId) => void;
|
|
934
|
+
onToggleProvider: (providerId: ProviderId, enable: boolean) => void;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const ProviderTreeItem: React.FC<ProviderTreeItemProps> = ({
|
|
938
|
+
providerId,
|
|
939
|
+
models,
|
|
940
|
+
enabledModels,
|
|
941
|
+
onToggleModel,
|
|
942
|
+
onToggleProvider,
|
|
943
|
+
}) => {
|
|
944
|
+
const enabledCount = models.filter((model) =>
|
|
945
|
+
enabledModels.has(new AiModelId(providerId, model.model).id),
|
|
946
|
+
).length;
|
|
947
|
+
const totalCount = models.length;
|
|
948
|
+
const maybeProviderInfo = AiModelRegistry.getProviderInfo(providerId);
|
|
949
|
+
const name = maybeProviderInfo?.name || Strings.startCase(providerId);
|
|
950
|
+
|
|
951
|
+
const checkboxState =
|
|
952
|
+
enabledCount === 0
|
|
953
|
+
? false
|
|
954
|
+
: enabledCount === totalCount
|
|
955
|
+
? true
|
|
956
|
+
: "indeterminate";
|
|
957
|
+
|
|
958
|
+
const handleProviderToggle = useEvent(() => {
|
|
959
|
+
const shouldEnable = enabledCount < totalCount / 2;
|
|
960
|
+
onToggleProvider(providerId, shouldEnable);
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
return (
|
|
964
|
+
<TreeItem
|
|
965
|
+
id={providerId}
|
|
966
|
+
hasChildItems={true}
|
|
967
|
+
textValue={providerId}
|
|
968
|
+
className="outline-none data-focused:bg-muted/50 group"
|
|
969
|
+
>
|
|
970
|
+
<TreeItemContent>
|
|
971
|
+
<div className="flex items-center gap-3 px-3 py-3 hover:bg-muted/50 cursor-pointer outline-none focus-visible:outline-none">
|
|
972
|
+
<Checkbox
|
|
973
|
+
checked={checkboxState}
|
|
974
|
+
onCheckedChange={handleProviderToggle}
|
|
975
|
+
onClick={Events.stopPropagation()}
|
|
976
|
+
/>
|
|
977
|
+
<AiProviderIcon provider={providerId} className="h-5 w-5" />
|
|
978
|
+
<div className="flex items-center justify-between w-full">
|
|
979
|
+
<h2 className="font-semibold">{name}</h2>
|
|
980
|
+
<p className="text-sm text-muted-secondary">
|
|
981
|
+
{enabledCount}/{totalCount} models
|
|
982
|
+
</p>
|
|
983
|
+
</div>
|
|
984
|
+
<AriaButton slot="chevron">
|
|
985
|
+
<ChevronRightIcon className="h-4 w-4 text-muted-foreground shrink-0 transition-transform duration-200 group-data-[expanded]:rotate-90" />
|
|
986
|
+
</AriaButton>
|
|
987
|
+
</div>
|
|
988
|
+
</TreeItemContent>
|
|
989
|
+
|
|
990
|
+
{models.map((model) => {
|
|
991
|
+
const qualifiedId = new AiModelId(providerId, model.model).id;
|
|
992
|
+
return (
|
|
993
|
+
<ModelListItem
|
|
994
|
+
key={qualifiedId}
|
|
995
|
+
qualifiedId={qualifiedId}
|
|
996
|
+
model={model}
|
|
997
|
+
isEnabled={enabledModels.has(qualifiedId)}
|
|
998
|
+
onToggle={onToggleModel}
|
|
999
|
+
/>
|
|
1000
|
+
);
|
|
1001
|
+
})}
|
|
1002
|
+
</TreeItem>
|
|
1003
|
+
);
|
|
1004
|
+
};
|
|
1005
|
+
|
|
1006
|
+
export const AiModelDisplayConfig: React.FC<AiConfigProps> = ({
|
|
1007
|
+
form,
|
|
1008
|
+
onSubmit,
|
|
1009
|
+
}) => {
|
|
1010
|
+
const aiModelRegistry = useMemo(
|
|
1011
|
+
() =>
|
|
1012
|
+
AiModelRegistry.create({
|
|
1013
|
+
displayedModels: [],
|
|
1014
|
+
customModels: ["openrouter/deepseek-r1-distill-llama-70b"],
|
|
1015
|
+
}),
|
|
1016
|
+
[],
|
|
1017
|
+
);
|
|
1018
|
+
const currentDisplayedModels = useWatch({
|
|
1019
|
+
control: form.control,
|
|
1020
|
+
name: "ai.models.displayed_models",
|
|
1021
|
+
defaultValue: [],
|
|
1022
|
+
}) as QualifiedModelId[];
|
|
1023
|
+
const currentDisplayedModelsSet = new Set(currentDisplayedModels);
|
|
1024
|
+
const modelsByProvider = aiModelRegistry.getGroupedModelsByProvider();
|
|
1025
|
+
|
|
1026
|
+
const toggleModelDisplay = useEvent((modelId: QualifiedModelId) => {
|
|
1027
|
+
const newModels = currentDisplayedModelsSet.has(modelId)
|
|
1028
|
+
? currentDisplayedModels.filter((id) => id !== modelId)
|
|
1029
|
+
: [...currentDisplayedModels, modelId];
|
|
1030
|
+
|
|
1031
|
+
form.setValue("ai.models.displayed_models", newModels);
|
|
1032
|
+
onSubmit(form.getValues());
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
const toggleProviderModels = useEvent(
|
|
1036
|
+
async (providerId: ProviderId, enable: boolean) => {
|
|
1037
|
+
const providerModels = modelsByProvider.get(providerId) || [];
|
|
1038
|
+
const qualifiedModelIds = new Set(
|
|
1039
|
+
providerModels.map((m) => new AiModelId(providerId, m.model).id),
|
|
1040
|
+
);
|
|
1041
|
+
|
|
1042
|
+
// If enabled, we add all provider models that aren't already enabled
|
|
1043
|
+
// Else, remove all provider models
|
|
1044
|
+
const newModels: QualifiedModelId[] = enable
|
|
1045
|
+
? [...new Set([...currentDisplayedModels, ...qualifiedModelIds])]
|
|
1046
|
+
: currentDisplayedModels.filter((id) => !qualifiedModelIds.has(id));
|
|
1047
|
+
|
|
1048
|
+
form.setValue("ai.models.displayed_models", newModels);
|
|
1049
|
+
onSubmit(form.getValues());
|
|
1050
|
+
},
|
|
1051
|
+
);
|
|
1052
|
+
|
|
1053
|
+
return (
|
|
1054
|
+
<SettingGroup>
|
|
1055
|
+
<p className="text-sm text-muted-secondary mb-4">
|
|
1056
|
+
Control which AI models are displayed in model selection dropdowns. When
|
|
1057
|
+
no models are selected, all available models will be shown.
|
|
1058
|
+
</p>
|
|
1059
|
+
|
|
1060
|
+
<div className="border rounded-md bg-background">
|
|
1061
|
+
<Tree
|
|
1062
|
+
aria-label="AI Models by Provider"
|
|
1063
|
+
className="flex-1 overflow-auto outline-none focus-visible:outline-none"
|
|
1064
|
+
selectionMode="none"
|
|
1065
|
+
>
|
|
1066
|
+
{[...modelsByProvider.entries()].map(([providerId, models]) => (
|
|
1067
|
+
<ProviderTreeItem
|
|
1068
|
+
key={providerId}
|
|
1069
|
+
providerId={providerId}
|
|
1070
|
+
models={models}
|
|
1071
|
+
enabledModels={currentDisplayedModelsSet}
|
|
1072
|
+
onToggleModel={toggleModelDisplay}
|
|
1073
|
+
onToggleProvider={toggleProviderModels}
|
|
1074
|
+
/>
|
|
1075
|
+
))}
|
|
1076
|
+
</Tree>
|
|
1077
|
+
</div>
|
|
1078
|
+
</SettingGroup>
|
|
1079
|
+
);
|
|
1080
|
+
};
|
|
1081
|
+
|
|
830
1082
|
export const AiConfig: React.FC<AiConfigProps> = ({
|
|
831
1083
|
form,
|
|
832
1084
|
config,
|
|
833
1085
|
onSubmit,
|
|
834
1086
|
}) => {
|
|
835
1087
|
return (
|
|
836
|
-
<Tabs defaultValue="ai-features">
|
|
1088
|
+
<Tabs defaultValue="ai-features" className="flex-1">
|
|
837
1089
|
<TabsList className="mb-2">
|
|
838
1090
|
<TabsTrigger value="ai-features">AI Features</TabsTrigger>
|
|
839
1091
|
<TabsTrigger value="ai-providers">AI Providers</TabsTrigger>
|
|
1092
|
+
<TabsTrigger value="ai-models">AI Models</TabsTrigger>
|
|
840
1093
|
</TabsList>
|
|
841
1094
|
|
|
842
1095
|
<TabsContent value="ai-features">
|
|
@@ -850,6 +1103,9 @@ export const AiConfig: React.FC<AiConfigProps> = ({
|
|
|
850
1103
|
<TabsContent value="ai-providers">
|
|
851
1104
|
<AiProvidersConfig form={form} config={config} onSubmit={onSubmit} />
|
|
852
1105
|
</TabsContent>
|
|
1106
|
+
<TabsContent value="ai-models">
|
|
1107
|
+
<AiModelDisplayConfig form={form} config={config} onSubmit={onSubmit} />
|
|
1108
|
+
</TabsContent>
|
|
853
1109
|
</Tabs>
|
|
854
1110
|
);
|
|
855
1111
|
};
|
|
@@ -51,7 +51,7 @@ export const ConfigButton: React.FC<Props> = ({
|
|
|
51
51
|
);
|
|
52
52
|
|
|
53
53
|
const userSettingsDialog = (
|
|
54
|
-
<DialogContent className="w-[
|
|
54
|
+
<DialogContent className="w-[90vw] h-[90vh] overflow-hidden sm:max-w-5xl top-[5vh] p-0">
|
|
55
55
|
<VisuallyHidden>
|
|
56
56
|
<DialogTitle>User settings</DialogTitle>
|
|
57
57
|
</VisuallyHidden>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
3
|
-
import { useEffect } from "react";
|
|
3
|
+
import { useEffect, useId } from "react";
|
|
4
4
|
import { useForm } from "react-hook-form";
|
|
5
5
|
import {
|
|
6
6
|
Form,
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import { useAppConfig } from "@/core/config/config";
|
|
15
15
|
import { getAppWidths } from "@/core/config/widths";
|
|
16
16
|
import { useRequestClient } from "@/core/network/requests";
|
|
17
|
+
import { useDebouncedCallback } from "@/hooks/useDebounce";
|
|
17
18
|
import { arrayToggle } from "@/utils/arrays";
|
|
18
19
|
import {
|
|
19
20
|
type AppConfig,
|
|
@@ -32,9 +33,13 @@ import {
|
|
|
32
33
|
SQL_OUTPUT_SELECT_OPTIONS,
|
|
33
34
|
} from "./common";
|
|
34
35
|
|
|
36
|
+
const FORM_DEBOUNCE = 100; // ms;
|
|
37
|
+
|
|
35
38
|
export const AppConfigForm: React.FC = () => {
|
|
36
39
|
const [config, setConfig] = useAppConfig();
|
|
37
40
|
const { saveAppConfig } = useRequestClient();
|
|
41
|
+
const htmlCheckboxId = useId();
|
|
42
|
+
const ipynbCheckboxId = useId();
|
|
38
43
|
|
|
39
44
|
// Create form
|
|
40
45
|
const form = useForm<AppConfig>({
|
|
@@ -52,6 +57,10 @@ export const AppConfigForm: React.FC = () => {
|
|
|
52
57
|
});
|
|
53
58
|
};
|
|
54
59
|
|
|
60
|
+
const debouncedSubmit = useDebouncedCallback((v: AppConfig) => {
|
|
61
|
+
onSubmit(v);
|
|
62
|
+
}, FORM_DEBOUNCE);
|
|
63
|
+
|
|
55
64
|
// When width is changed, dispatch a resize event so widgets know to resize
|
|
56
65
|
useEffect(() => {
|
|
57
66
|
window.dispatchEvent(new Event("resize"));
|
|
@@ -60,7 +69,7 @@ export const AppConfigForm: React.FC = () => {
|
|
|
60
69
|
return (
|
|
61
70
|
<Form {...form}>
|
|
62
71
|
<form
|
|
63
|
-
onChange={form.handleSubmit(
|
|
72
|
+
onChange={form.handleSubmit(debouncedSubmit)}
|
|
64
73
|
className="flex flex-col gap-6"
|
|
65
74
|
>
|
|
66
75
|
<div>
|
|
@@ -253,23 +262,25 @@ export const AppConfigForm: React.FC = () => {
|
|
|
253
262
|
<div className="flex gap-4">
|
|
254
263
|
<div className="flex items-center space-x-2">
|
|
255
264
|
<Checkbox
|
|
256
|
-
id=
|
|
265
|
+
id={htmlCheckboxId}
|
|
266
|
+
data-testid="html-checkbox"
|
|
257
267
|
checked={field.value.includes("html")}
|
|
258
268
|
onCheckedChange={() => {
|
|
259
269
|
field.onChange(arrayToggle(field.value, "html"));
|
|
260
270
|
}}
|
|
261
271
|
/>
|
|
262
|
-
<FormLabel htmlFor=
|
|
272
|
+
<FormLabel htmlFor={htmlCheckboxId}>HTML</FormLabel>
|
|
263
273
|
</div>
|
|
264
274
|
<div className="flex items-center space-x-2">
|
|
265
275
|
<Checkbox
|
|
266
|
-
id=
|
|
276
|
+
id={ipynbCheckboxId}
|
|
277
|
+
data-testid="ipynb-checkbox"
|
|
267
278
|
checked={field.value.includes("ipynb")}
|
|
268
279
|
onCheckedChange={() => {
|
|
269
280
|
field.onChange(arrayToggle(field.value, "ipynb"));
|
|
270
281
|
}}
|
|
271
282
|
/>
|
|
272
|
-
<FormLabel htmlFor=
|
|
283
|
+
<FormLabel htmlFor={ipynbCheckboxId}>IPYNB</FormLabel>
|
|
273
284
|
</div>
|
|
274
285
|
</div>
|
|
275
286
|
</FormControl>
|
|
@@ -41,6 +41,7 @@ import { getAppWidths } from "@/core/config/widths";
|
|
|
41
41
|
import { marimoVersionAtom } from "@/core/meta/state";
|
|
42
42
|
import { useRequestClient } from "@/core/network/requests";
|
|
43
43
|
import { isWasm } from "@/core/wasm/utils";
|
|
44
|
+
import { useDebouncedCallback } from "@/hooks/useDebounce";
|
|
44
45
|
import { Banner } from "@/plugins/impl/common/error-banner";
|
|
45
46
|
import { THEMES } from "@/theme/useTheme";
|
|
46
47
|
import { arrayToggle } from "@/utils/arrays";
|
|
@@ -106,6 +107,8 @@ export const activeUserConfigCategoryAtom = atom<SettingCategoryId>(
|
|
|
106
107
|
categories[0].id,
|
|
107
108
|
);
|
|
108
109
|
|
|
110
|
+
const FORM_DEBOUNCE = 100; // ms;
|
|
111
|
+
|
|
109
112
|
export const UserConfigForm: React.FC = () => {
|
|
110
113
|
const [config, setConfig] = useUserConfig();
|
|
111
114
|
const formElement = useRef<HTMLFormElement>(null);
|
|
@@ -123,11 +126,12 @@ export const UserConfigForm: React.FC = () => {
|
|
|
123
126
|
defaultValues: config,
|
|
124
127
|
});
|
|
125
128
|
|
|
126
|
-
const
|
|
129
|
+
const onSubmitNotDebounced = async (values: UserConfig) => {
|
|
127
130
|
await saveUserConfig({ config: values }).then(() => {
|
|
128
131
|
setConfig(values);
|
|
129
132
|
});
|
|
130
133
|
};
|
|
134
|
+
const onSubmit = useDebouncedCallback(onSubmitNotDebounced, FORM_DEBOUNCE);
|
|
131
135
|
|
|
132
136
|
const isWasmRuntime = isWasm();
|
|
133
137
|
const htmlCheckboxId = useId();
|