@marimo-team/frontend 0.14.18-dev24 → 0.14.18-dev25
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/assets/{ConnectedDataExplorerComponent-Dbu2xXc5.js → ConnectedDataExplorerComponent-uUW_oa-n.js} +1 -1
- package/dist/assets/{ImageComparisonComponent-BkZIjGHA.js → ImageComparisonComponent-D3rn2HCY.js} +1 -1
- package/dist/assets/{VegaLite-Ca7AXGyA.js → VegaLite-C81SYXSH.js} +1 -1
- package/dist/assets/{_baseEach-H5Qk1V2B.js → _baseEach-CCKARmjk.js} +1 -1
- package/dist/assets/_baseMap-DHFHd42n.js +1 -0
- package/dist/assets/{_baseUniq-UAmxGez2.js → _baseUniq-Ct6AJG4k.js} +1 -1
- package/dist/assets/{_createAggregator-H-t5qYSG.js → _createAggregator-D9sUI_I2.js} +1 -1
- package/dist/assets/{any-language-editor-DUt-pIdy.js → any-language-editor-D48TLVli.js} +1 -1
- package/dist/assets/{architectureDiagram-SUXI7LT5-DWP05q8_.js → architectureDiagram-SUXI7LT5-DsHbpG7-.js} +1 -1
- package/dist/assets/{blockDiagram-6J76NXCF-DB57b3LI.js → blockDiagram-6J76NXCF-miF3EQqD.js} +1 -1
- package/dist/assets/{c4Diagram-6F6E4RAY-ByeAGPmY.js → c4Diagram-6F6E4RAY-DDyzKT7I.js} +1 -1
- package/dist/assets/channel-Dp-bN8Tn.js +1 -0
- package/dist/assets/{chunk-353BL4L5-ngKqumF3.js → chunk-353BL4L5-C3r6f9qX.js} +1 -1
- package/dist/assets/{chunk-67H74DCK-DTbVgB4A.js → chunk-67H74DCK-Bejh7gMt.js} +1 -1
- package/dist/assets/{chunk-AACKK3MU-t9Kf_p_V.js → chunk-AACKK3MU-sMVfID3D.js} +1 -1
- package/dist/assets/{chunk-BFAMUDN2-B0dcgdMs.js → chunk-BFAMUDN2-CPrzh58T.js} +1 -1
- package/dist/assets/{chunk-E2GYISFI-C7n5SQvq.js → chunk-E2GYISFI-CpRIm91o.js} +1 -1
- package/dist/assets/{chunk-OW32GOEJ-DEvBFIh8.js → chunk-OW32GOEJ--NsOOyke.js} +1 -1
- package/dist/assets/{chunk-SKB7J2MH-DX71TF_n.js → chunk-SKB7J2MH-VEIhIqAb.js} +1 -1
- package/dist/assets/{chunk-SZ463SBG-CO9fYjVe.js → chunk-SZ463SBG-D5dyDk77.js} +1 -1
- package/dist/assets/{circle-play-B-ZWQLkW.js → circle-play-CG2MKDA7.js} +1 -1
- package/dist/assets/classDiagram-M3E45YP4-CbrErnga.js +1 -0
- package/dist/assets/classDiagram-v2-YAWTLIQI-CbrErnga.js +1 -0
- package/dist/assets/clone-Bwreh4_E.js +1 -0
- package/dist/assets/{compile-lv6gzALt.js → compile-DP96rHJE.js} +1 -1
- package/dist/assets/{dagre-JOIXM2OF-B7ZtMRIJ.js → dagre-JOIXM2OF-0pjdswA-.js} +1 -1
- package/dist/assets/{data-grid-overlay-editor-DTAq6v9P.js → data-grid-overlay-editor-Brdcr4Cg.js} +1 -1
- package/dist/assets/{diagram-5UYTHUR4-CboeMF3G.js → diagram-5UYTHUR4-DaSfveJw.js} +1 -1
- package/dist/assets/{diagram-VMROVX33-BcnQcTFp.js → diagram-VMROVX33-DbPgVdVv.js} +1 -1
- package/dist/assets/{diagram-ZTM2IBQH-CaFT3B8g.js → diagram-ZTM2IBQH-AI1tyxQl.js} +1 -1
- package/dist/assets/{edit-page-CvWZ8wVZ.js → edit-page-DVUbxAiV.js} +53 -53
- package/dist/assets/{erDiagram-3M52JZNH-BFPc1-ng.js → erDiagram-3M52JZNH-B-bvH0zO.js} +1 -1
- package/dist/assets/{flowDiagram-KYDEHFYC-CAZBj4fC.js → flowDiagram-KYDEHFYC-00godu9K.js} +1 -1
- package/dist/assets/{ganttDiagram-EK5VF46D-CUxffTMj.js → ganttDiagram-EK5VF46D-BQ1iBKwi.js} +1 -1
- package/dist/assets/{gitGraphDiagram-GW3U2K7C-Tx-ZfecV.js → gitGraphDiagram-GW3U2K7C-D5Tz2vMb.js} +1 -1
- package/dist/assets/{glide-data-editor-BP8l5q3f.js → glide-data-editor-DlFhFA5K.js} +11 -11
- package/dist/assets/{graph-Bf6eoL3M.js → graph-DILKkkuJ.js} +1 -1
- package/dist/assets/{home-page--ixzxF-w.js → home-page-DfUEt5SZ.js} +1 -1
- package/dist/assets/{index-Q73xW9dd.js → index-3u3F_pc_.js} +1 -1
- package/dist/assets/{index-dvYU8Vev.js → index-B-0VrQJs.js} +94 -94
- package/dist/assets/index-B3Q6PaCG.css +1 -0
- package/dist/assets/{index-U0KSor5u.js → index-BUdpsSTm.js} +1 -1
- package/dist/assets/{index-DleA5-JR.js → index-BjFS2OOX.js} +1 -1
- package/dist/assets/{index-CM8rF_ge.js → index-Bje05tll.js} +1 -1
- package/dist/assets/{index-B39Q37Jh.js → index-Bm5qdklD.js} +1 -1
- package/dist/assets/{index-kaWF-HKt.js → index-CTY2I0Yv.js} +1 -1
- package/dist/assets/{index-mvfeSH0B.js → index-CXwIwO75.js} +1 -1
- package/dist/assets/{index-Uh_QGNQm.js → index-CmJl_54o.js} +1 -1
- package/dist/assets/{index-C4iPAevr.js → index-DKAkPOzF.js} +1 -1
- package/dist/assets/{index-DhU7edG1.js → index-DMPtgxYL.js} +1 -1
- package/dist/assets/{index-D1vinr8C.js → index-De0Y6uVY.js} +1 -1
- package/dist/assets/{index-BMLuYuQT.js → index-DjWXyjjL.js} +1 -1
- package/dist/assets/{index-q4AwmgtF.js → index-Dy9b9CBe.js} +1 -1
- package/dist/assets/{index-CmJm6kj5.js → index-GN_ls_Ca.js} +1 -1
- package/dist/assets/{index-DupjaVjo.js → index-Wo8BkCCL.js} +1 -1
- package/dist/assets/{index-g-JZ5Z-_.js → index-Zqgca8xb.js} +1 -1
- package/dist/assets/{index-Cv01rAR1.js → index-roXogAjj.js} +1 -1
- package/dist/assets/{index-DJ1FNShi.js → index-zgsv7Wc7.js} +1 -1
- package/dist/assets/infoDiagram-LHK5PUON-Jii4Xmvn.js +2 -0
- package/dist/assets/{journeyDiagram-EWQZEKCU-DBorgqR8.js → journeyDiagram-EWQZEKCU-seuSICJD.js} +1 -1
- package/dist/assets/{kanban-definition-ZSS6B67P-rOei7rdW.js → kanban-definition-ZSS6B67P-BFjlBPnQ.js} +1 -1
- package/dist/assets/{layout-D5U1vfFv.js → layout-Da1rMK06.js} +1 -1
- package/dist/assets/{linear-BPaO6rYC.js → linear-Cf4m4pdY.js} +1 -1
- package/dist/assets/links-C2UZ92jn.js +18 -0
- package/dist/assets/{mermaid-DuiiiGkf.js → mermaid-upQivttk.js} +4 -4
- package/dist/assets/{min-GM8d8p3k.js → min-CSxPTKuA.js} +1 -1
- package/dist/assets/{mindmap-definition-6CBA2TL7-DFNMYbU5.js → mindmap-definition-6CBA2TL7-BqjV1n_A.js} +1 -1
- package/dist/assets/{number-overlay-editor-CcD_5O9P.js → number-overlay-editor-7iSsv6oT.js} +1 -1
- package/dist/assets/{pieDiagram-NIOCPIFQ-BAnWQSTZ.js → pieDiagram-NIOCPIFQ-CQ5LrDZz.js} +1 -1
- package/dist/assets/{quadrantDiagram-2OG54O6I-CLzghZ4d.js → quadrantDiagram-2OG54O6I-7PMNuAjn.js} +1 -1
- package/dist/assets/{react-plotly-qIomJONw.js → react-plotly-v2f3cnH0.js} +1 -1
- package/dist/assets/{requirementDiagram-QOLK2EJ7-ClKsOuLQ.js → requirementDiagram-QOLK2EJ7-rs-_sls-.js} +1 -1
- package/dist/assets/{run-page-B9ntqQci.js → run-page-oQHKY6wX.js} +1 -1
- package/dist/assets/{sankeyDiagram-4UZDY2LN-CMUyusMd.js → sankeyDiagram-4UZDY2LN-DeKeOc4P.js} +1 -1
- package/dist/assets/{sequenceDiagram-SKLFT4DO-BUL7OokF.js → sequenceDiagram-SKLFT4DO-DpBetwFn.js} +1 -1
- package/dist/assets/{slides-component-DxNxYl9E.js → slides-component-B8jVQNPd.js} +1 -1
- package/dist/assets/{sortBy-Bo672N53.js → sortBy-Chg88WWD.js} +1 -1
- package/dist/assets/{stateDiagram-MI5ZYTHO-CK5D03xc.js → stateDiagram-MI5ZYTHO-C4Mfh1a2.js} +1 -1
- package/dist/assets/stateDiagram-v2-5AN5P6BG-Bbw2B1Vc.js +1 -0
- package/dist/assets/{storage-DCGJ86_2.js → storage-MSc_Sfm1.js} +3 -3
- package/dist/assets/{terminal-C9ZYVCQk.js → terminal-9jZQ3IDc.js} +1 -1
- package/dist/assets/{time-DUBsogDP.js → time-D8GAmv2E.js} +1 -1
- package/dist/assets/{timeline-definition-MYPXXCX6-DlOzuKHL.js → timeline-definition-MYPXXCX6-BEQaSzX_.js} +1 -1
- package/dist/assets/{tracing-DkB9iogQ.js → tracing-2DOPh4d7.js} +2 -2
- package/dist/assets/{trash-CuNNSzF1.js → trash-yfzCnI7m.js} +1 -1
- package/dist/assets/{treemap-75Q7IDZK-DUsN_Z4F.js → treemap-75Q7IDZK-0Nh4W7l5.js} +1 -1
- package/dist/assets/{vega-component-DByprFwW.js → vega-component-Bj1KedcS.js} +1 -1
- package/dist/assets/{xychartDiagram-H2YORKM3-BaUqYAb6.js → xychartDiagram-H2YORKM3-CbP3FZsC.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/components/ai/ai-model-dropdown.tsx +288 -0
- package/src/components/ai/ai-provider-icon.tsx +7 -4
- package/src/components/app-config/ai-config.tsx +100 -76
- package/src/components/app-config/app-config-button.tsx +10 -1
- package/src/components/app-config/constants.ts +0 -34
- package/src/components/app-config/incorrect-model-id.tsx +4 -2
- package/src/components/app-config/user-config-form.tsx +12 -5
- package/src/components/chat/chat-panel.tsx +12 -26
- package/src/core/ai/__tests__/model-registry.test.ts +357 -0
- package/src/{utils/ai → core/ai/ids}/__tests__/ids.test.ts +2 -1
- package/src/{utils/ai → core/ai/ids}/ids.ts +18 -10
- package/src/core/ai/model-registry.ts +164 -0
- package/src/core/cells/effects.ts +1 -1
- package/src/utils/__tests__/multi-map.test.ts +295 -0
- package/src/utils/multi-map.ts +71 -0
- package/dist/assets/_baseMap-lEtQfieX.js +0 -1
- package/dist/assets/channel-CJdgPvjM.js +0 -1
- package/dist/assets/classDiagram-M3E45YP4-Tb8oQ03C.js +0 -1
- package/dist/assets/classDiagram-v2-YAWTLIQI-Tb8oQ03C.js +0 -1
- package/dist/assets/clone-DygFoMzB.js +0 -1
- package/dist/assets/index-BlxPam9h.css +0 -1
- package/dist/assets/infoDiagram-LHK5PUON-Cz497oaY.js +0 -2
- package/dist/assets/links-Cxxlu7np.js +0 -17
- package/dist/assets/stateDiagram-v2-5AN5P6BG-Deqw4NDh.js +0 -1
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { Role } from "@marimo-team/llm-info";
|
|
4
|
+
import { capitalize } from "lodash-es";
|
|
5
|
+
import { ChevronDownIcon, CircleHelpIcon } from "lucide-react";
|
|
6
|
+
import {
|
|
7
|
+
AiModelId,
|
|
8
|
+
isKnownAIProvider,
|
|
9
|
+
type ProviderId,
|
|
10
|
+
type QualifiedModelId,
|
|
11
|
+
} from "@/core/ai/ids/ids";
|
|
12
|
+
import { type AiModel, AiModelRegistry } from "@/core/ai/model-registry";
|
|
13
|
+
import { useResolvedMarimoConfig } from "@/core/config/config";
|
|
14
|
+
import {
|
|
15
|
+
DropdownMenu,
|
|
16
|
+
DropdownMenuContent,
|
|
17
|
+
DropdownMenuItem,
|
|
18
|
+
DropdownMenuPortal,
|
|
19
|
+
DropdownMenuSeparator,
|
|
20
|
+
DropdownMenuSub,
|
|
21
|
+
DropdownMenuSubContent,
|
|
22
|
+
DropdownMenuSubTrigger,
|
|
23
|
+
DropdownMenuTrigger,
|
|
24
|
+
} from "../ui/dropdown-menu";
|
|
25
|
+
import { AiProviderIcon } from "./ai-provider-icon";
|
|
26
|
+
|
|
27
|
+
interface AIModelDropdownProps {
|
|
28
|
+
value?: string;
|
|
29
|
+
placeholder?: string;
|
|
30
|
+
onSelect: (modelId: QualifiedModelId) => void;
|
|
31
|
+
triggerClassName?: string;
|
|
32
|
+
customDropdownContent?: React.ReactNode;
|
|
33
|
+
iconSize?: "medium" | "small";
|
|
34
|
+
showAddCustomModelDocs?: boolean;
|
|
35
|
+
forRole?: Role;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const AIModelDropdown = ({
|
|
39
|
+
value,
|
|
40
|
+
placeholder,
|
|
41
|
+
onSelect,
|
|
42
|
+
triggerClassName,
|
|
43
|
+
customDropdownContent,
|
|
44
|
+
iconSize = "medium",
|
|
45
|
+
showAddCustomModelDocs = false,
|
|
46
|
+
forRole,
|
|
47
|
+
}: AIModelDropdownProps) => {
|
|
48
|
+
const currentValue = value ? AiModelId.parse(value) : undefined;
|
|
49
|
+
|
|
50
|
+
const selectModel = (modelId: QualifiedModelId) => {
|
|
51
|
+
onSelect(modelId);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const [marimoConfig] = useResolvedMarimoConfig();
|
|
55
|
+
const configModels = marimoConfig.ai?.models;
|
|
56
|
+
|
|
57
|
+
// Only include autocompleteModel if copilot is set to "custom"
|
|
58
|
+
const autocompleteModel =
|
|
59
|
+
marimoConfig.completion.copilot === "custom"
|
|
60
|
+
? configModels?.autocomplete_model
|
|
61
|
+
: undefined;
|
|
62
|
+
|
|
63
|
+
const aiModelRegistry = AiModelRegistry.create({
|
|
64
|
+
// We add all the custom models and the models used in the editor.
|
|
65
|
+
// If they among the known models, they won't overwrite them.
|
|
66
|
+
customModels: [
|
|
67
|
+
...(configModels?.custom_models ?? []),
|
|
68
|
+
configModels?.chat_model,
|
|
69
|
+
autocompleteModel,
|
|
70
|
+
configModels?.edit_model,
|
|
71
|
+
].filter(Boolean),
|
|
72
|
+
displayedModels: configModels?.displayed_models,
|
|
73
|
+
});
|
|
74
|
+
const modelsByProvider = aiModelRegistry.getGroupedModelsByProvider();
|
|
75
|
+
|
|
76
|
+
const activeModel =
|
|
77
|
+
forRole === "autocomplete"
|
|
78
|
+
? configModels?.autocomplete_model
|
|
79
|
+
: forRole === "chat"
|
|
80
|
+
? configModels?.chat_model
|
|
81
|
+
: forRole === "edit"
|
|
82
|
+
? configModels?.edit_model
|
|
83
|
+
: undefined;
|
|
84
|
+
|
|
85
|
+
const iconSizeClass = iconSize === "medium" ? "h-4 w-4" : "h-3 w-3";
|
|
86
|
+
|
|
87
|
+
const renderModelWithRole = (modelId: AiModelId, role: Role) => {
|
|
88
|
+
const maybeModelMatch = aiModelRegistry.getModel(modelId.id);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="flex items-center gap-2 w-full px-2 py-1">
|
|
92
|
+
<AiProviderIcon
|
|
93
|
+
provider={modelId.providerId}
|
|
94
|
+
className={iconSizeClass}
|
|
95
|
+
/>
|
|
96
|
+
<div className="flex flex-col">
|
|
97
|
+
<span>{maybeModelMatch?.name || modelId.shortModelId}</span>
|
|
98
|
+
<span className="text-xs text-muted-foreground">{modelId.id}</span>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div className="ml-auto flex gap-1">
|
|
102
|
+
<span
|
|
103
|
+
key={role}
|
|
104
|
+
className={`text-xs px-1.5 py-0.5 rounded font-medium ${getTagColour(role)}`}
|
|
105
|
+
>
|
|
106
|
+
{role}
|
|
107
|
+
</span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<DropdownMenu>
|
|
115
|
+
<DropdownMenuTrigger
|
|
116
|
+
className={`flex items-center justify-between px-2 py-0.5 border rounded-md
|
|
117
|
+
hover:bg-accent hover:text-accent-foreground ${triggerClassName}`}
|
|
118
|
+
>
|
|
119
|
+
<div className="flex items-center gap-2">
|
|
120
|
+
{currentValue ? (
|
|
121
|
+
<>
|
|
122
|
+
<AiProviderIcon
|
|
123
|
+
provider={currentValue.providerId}
|
|
124
|
+
className={iconSizeClass}
|
|
125
|
+
/>
|
|
126
|
+
<span className="truncate">
|
|
127
|
+
{isKnownAIProvider(currentValue.providerId)
|
|
128
|
+
? currentValue.shortModelId
|
|
129
|
+
: currentValue.id}
|
|
130
|
+
</span>
|
|
131
|
+
</>
|
|
132
|
+
) : (
|
|
133
|
+
<span className="text-muted-foreground truncate">
|
|
134
|
+
{placeholder}
|
|
135
|
+
</span>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
<ChevronDownIcon className={`${iconSizeClass} ml-1`} />
|
|
139
|
+
</DropdownMenuTrigger>
|
|
140
|
+
|
|
141
|
+
<DropdownMenuContent className="w-[300px]">
|
|
142
|
+
{activeModel &&
|
|
143
|
+
forRole &&
|
|
144
|
+
renderModelWithRole(AiModelId.parse(activeModel), forRole)}
|
|
145
|
+
{activeModel && forRole && <DropdownMenuSeparator />}
|
|
146
|
+
|
|
147
|
+
{[...modelsByProvider.entries()].map(([provider, models]) => (
|
|
148
|
+
<ProviderDropdownContent
|
|
149
|
+
key={provider}
|
|
150
|
+
provider={provider}
|
|
151
|
+
onSelect={selectModel}
|
|
152
|
+
models={models}
|
|
153
|
+
iconSizeClass={iconSizeClass}
|
|
154
|
+
/>
|
|
155
|
+
))}
|
|
156
|
+
|
|
157
|
+
{customDropdownContent}
|
|
158
|
+
|
|
159
|
+
{showAddCustomModelDocs && (
|
|
160
|
+
<>
|
|
161
|
+
<DropdownMenuSeparator />
|
|
162
|
+
<DropdownMenuItem className="flex items-center gap-2">
|
|
163
|
+
<a
|
|
164
|
+
className="flex items-center gap-1"
|
|
165
|
+
href="https://docs.marimo.io/guides/editor_features/ai_completion/?h=models#other-ai-providers"
|
|
166
|
+
target="_blank"
|
|
167
|
+
rel="noreferrer"
|
|
168
|
+
>
|
|
169
|
+
<CircleHelpIcon className="h-3 w-3" />
|
|
170
|
+
<span>How to add a custom model</span>
|
|
171
|
+
</a>
|
|
172
|
+
</DropdownMenuItem>
|
|
173
|
+
</>
|
|
174
|
+
)}
|
|
175
|
+
</DropdownMenuContent>
|
|
176
|
+
</DropdownMenu>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const ProviderDropdownContent = ({
|
|
181
|
+
provider,
|
|
182
|
+
onSelect,
|
|
183
|
+
models,
|
|
184
|
+
customModelIcon,
|
|
185
|
+
iconSizeClass,
|
|
186
|
+
}: {
|
|
187
|
+
provider: ProviderId;
|
|
188
|
+
onSelect: (modelId: QualifiedModelId) => void;
|
|
189
|
+
models: AiModel[];
|
|
190
|
+
customModelIcon?: React.ReactNode;
|
|
191
|
+
iconSizeClass: string;
|
|
192
|
+
}) => {
|
|
193
|
+
const iconProvider = isKnownAIProvider(provider)
|
|
194
|
+
? provider
|
|
195
|
+
: "openai-compatible";
|
|
196
|
+
|
|
197
|
+
const maybeProviderInfo = AiModelRegistry.getProviderInfo(provider);
|
|
198
|
+
|
|
199
|
+
if (models.length === 0) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<DropdownMenuSub>
|
|
205
|
+
<DropdownMenuSubTrigger>
|
|
206
|
+
<p className="flex items-center gap-2">
|
|
207
|
+
<AiProviderIcon provider={iconProvider} className={iconSizeClass} />
|
|
208
|
+
{getProviderLabel(provider)}
|
|
209
|
+
</p>
|
|
210
|
+
</DropdownMenuSubTrigger>
|
|
211
|
+
<DropdownMenuPortal>
|
|
212
|
+
<DropdownMenuSubContent
|
|
213
|
+
className="max-h-[40vh] overflow-y-auto"
|
|
214
|
+
alignOffset={-90}
|
|
215
|
+
>
|
|
216
|
+
{maybeProviderInfo && (
|
|
217
|
+
<>
|
|
218
|
+
<p className="text-sm text-muted-foreground p-2 max-w-[300px]">
|
|
219
|
+
{maybeProviderInfo.description}
|
|
220
|
+
<br />
|
|
221
|
+
</p>
|
|
222
|
+
|
|
223
|
+
<p className="text-sm text-muted-foreground p-2 pt-0">
|
|
224
|
+
You can find more information about this provider{" "}
|
|
225
|
+
<a
|
|
226
|
+
href={maybeProviderInfo.url}
|
|
227
|
+
target="_blank"
|
|
228
|
+
className="underline"
|
|
229
|
+
rel="noreferrer"
|
|
230
|
+
>
|
|
231
|
+
here
|
|
232
|
+
</a>
|
|
233
|
+
.
|
|
234
|
+
</p>
|
|
235
|
+
<DropdownMenuSeparator />
|
|
236
|
+
</>
|
|
237
|
+
)}
|
|
238
|
+
{models.map((model) => {
|
|
239
|
+
const qualifiedModelId =
|
|
240
|
+
`${provider}/${model.model}` as QualifiedModelId;
|
|
241
|
+
return (
|
|
242
|
+
<DropdownMenuItem
|
|
243
|
+
key={qualifiedModelId}
|
|
244
|
+
className="flex items-center gap-2"
|
|
245
|
+
onSelect={(e) => {
|
|
246
|
+
onSelect(qualifiedModelId);
|
|
247
|
+
}}
|
|
248
|
+
onClick={(e) => {
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
onSelect(qualifiedModelId);
|
|
251
|
+
}}
|
|
252
|
+
>
|
|
253
|
+
<AiProviderIcon provider={iconProvider} className="h-4 w-4" />
|
|
254
|
+
<div className="pl-1 flex flex-col">
|
|
255
|
+
<span>{model.name}</span>
|
|
256
|
+
<span className="text-xs text-muted-foreground">
|
|
257
|
+
{model.model}
|
|
258
|
+
</span>
|
|
259
|
+
</div>
|
|
260
|
+
{model.custom && customModelIcon}
|
|
261
|
+
</DropdownMenuItem>
|
|
262
|
+
);
|
|
263
|
+
})}
|
|
264
|
+
</DropdownMenuSubContent>
|
|
265
|
+
</DropdownMenuPortal>
|
|
266
|
+
</DropdownMenuSub>
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
function getProviderLabel(provider: ProviderId): string {
|
|
271
|
+
const providerInfo = AiModelRegistry.getProviderInfo(provider);
|
|
272
|
+
if (providerInfo) {
|
|
273
|
+
return providerInfo.name;
|
|
274
|
+
}
|
|
275
|
+
return capitalize(provider);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function getTagColour(role: Role): string {
|
|
279
|
+
switch (role) {
|
|
280
|
+
case "chat":
|
|
281
|
+
return "bg-[var(--purple-3)] text-[var(--purple-11)]";
|
|
282
|
+
case "autocomplete":
|
|
283
|
+
return "bg-[var(--green-3)] text-[var(--green-11)]";
|
|
284
|
+
case "edit":
|
|
285
|
+
return "bg-[var(--blue-3)] text-[var(--blue-11)]";
|
|
286
|
+
}
|
|
287
|
+
return "bg-[var(--mauve-3)] text-[var(--mauve-11)]";
|
|
288
|
+
}
|
|
@@ -3,20 +3,23 @@
|
|
|
3
3
|
import AnthropicIcon from "@marimo-team/llm-info/icons/anthropic.svg?inline";
|
|
4
4
|
import BedrockIcon from "@marimo-team/llm-info/icons/aws.svg?inline";
|
|
5
5
|
import AzureIcon from "@marimo-team/llm-info/icons/azure.svg?inline";
|
|
6
|
-
import
|
|
6
|
+
import DeepseekIcon from "@marimo-team/llm-info/icons/deepseek.svg?inline";
|
|
7
|
+
import GeminiIcon from "@marimo-team/llm-info/icons/googlegemini.svg?inline";
|
|
7
8
|
import OllamaIcon from "@marimo-team/llm-info/icons/ollama.svg?inline";
|
|
8
9
|
import OpenAIIcon from "@marimo-team/llm-info/icons/openai.svg?inline";
|
|
9
10
|
import { BotIcon } from "lucide-react";
|
|
10
11
|
import * as React from "react";
|
|
12
|
+
import type { ProviderId } from "@/core/ai/ids/ids";
|
|
11
13
|
import { cn } from "@/utils/cn";
|
|
12
14
|
|
|
13
|
-
const icons = {
|
|
15
|
+
const icons: Record<ProviderId, string> = {
|
|
14
16
|
openai: OpenAIIcon,
|
|
15
17
|
anthropic: AnthropicIcon,
|
|
16
|
-
google:
|
|
18
|
+
google: GeminiIcon,
|
|
17
19
|
ollama: OllamaIcon,
|
|
18
20
|
azure: AzureIcon,
|
|
19
21
|
bedrock: BedrockIcon,
|
|
22
|
+
deepseek: DeepseekIcon,
|
|
20
23
|
};
|
|
21
24
|
|
|
22
25
|
export interface AiProviderIconProps
|
|
@@ -30,7 +33,7 @@ export const AiProviderIcon: React.FC<AiProviderIconProps> = ({
|
|
|
30
33
|
className = "",
|
|
31
34
|
...props
|
|
32
35
|
}) => {
|
|
33
|
-
if (provider === "openai-compatible") {
|
|
36
|
+
if (provider === "openai-compatible" || !(provider in icons)) {
|
|
34
37
|
return <BotIcon className={cn("h-4 w-4", className)} />;
|
|
35
38
|
}
|
|
36
39
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { InfoIcon } from "lucide-react";
|
|
4
|
+
import React from "react";
|
|
4
5
|
import type { FieldPath, UseFormReturn } from "react-hook-form";
|
|
5
6
|
import {
|
|
6
7
|
FormControl,
|
|
7
8
|
FormDescription,
|
|
9
|
+
FormErrorsBanner,
|
|
8
10
|
FormField,
|
|
9
11
|
FormItem,
|
|
10
12
|
FormLabel,
|
|
@@ -14,9 +16,12 @@ import { Input } from "@/components/ui/input";
|
|
|
14
16
|
import { Kbd } from "@/components/ui/kbd";
|
|
15
17
|
import { NativeSelect } from "@/components/ui/native-select";
|
|
16
18
|
import { Textarea } from "@/components/ui/textarea";
|
|
19
|
+
import type { QualifiedModelId } from "@/core/ai/ids/ids";
|
|
17
20
|
import { CopilotConfig } from "@/core/codemirror/copilot/copilot-config";
|
|
18
21
|
import { DEFAULT_AI_MODEL, type UserConfig } from "@/core/config/config-schema";
|
|
19
22
|
import { isWasm } from "@/core/wasm/utils";
|
|
23
|
+
import { Events } from "@/utils/events";
|
|
24
|
+
import { AIModelDropdown } from "../ai/ai-model-dropdown";
|
|
20
25
|
import {
|
|
21
26
|
AiProviderIcon,
|
|
22
27
|
type AiProviderIconProps,
|
|
@@ -27,10 +32,12 @@ import {
|
|
|
27
32
|
AccordionItem,
|
|
28
33
|
AccordionTrigger,
|
|
29
34
|
} from "../ui/accordion";
|
|
35
|
+
import { DropdownMenuSeparator } from "../ui/dropdown-menu";
|
|
30
36
|
import { ExternalLink } from "../ui/links";
|
|
31
37
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
|
38
|
+
import { Tooltip } from "../ui/tooltip";
|
|
32
39
|
import { SettingSubtitle } from "./common";
|
|
33
|
-
import { AWS_REGIONS
|
|
40
|
+
import { AWS_REGIONS } from "./constants";
|
|
34
41
|
import { IncorrectModelId } from "./incorrect-model-id";
|
|
35
42
|
import { IsOverridden } from "./is-overridden";
|
|
36
43
|
|
|
@@ -182,6 +189,7 @@ interface ModelSelectorProps {
|
|
|
182
189
|
description?: React.ReactNode;
|
|
183
190
|
disabled?: boolean;
|
|
184
191
|
label: string;
|
|
192
|
+
onSubmit: (values: UserConfig) => Promise<void>;
|
|
185
193
|
}
|
|
186
194
|
|
|
187
195
|
export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|
@@ -193,42 +201,72 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|
|
193
201
|
description,
|
|
194
202
|
disabled = false,
|
|
195
203
|
label,
|
|
204
|
+
onSubmit,
|
|
196
205
|
}) => {
|
|
197
|
-
const modelInputId = useId();
|
|
198
|
-
|
|
199
206
|
return (
|
|
200
207
|
<FormField
|
|
201
208
|
control={form.control}
|
|
202
209
|
name={name}
|
|
203
210
|
disabled={disabled}
|
|
204
|
-
render={({ field }) =>
|
|
205
|
-
|
|
211
|
+
render={({ field }) => {
|
|
212
|
+
const value = asStringOrUndefined(field.value);
|
|
213
|
+
|
|
214
|
+
const selectModel = (modelId: QualifiedModelId) => {
|
|
215
|
+
field.onChange(modelId);
|
|
216
|
+
onSubmit(form.getValues());
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const renderFormItem = () => (
|
|
206
220
|
<FormItem className={formItemClasses}>
|
|
207
221
|
<FormLabel>{label}</FormLabel>
|
|
208
222
|
<FormControl>
|
|
209
|
-
<
|
|
210
|
-
|
|
211
|
-
data-testid={testId}
|
|
212
|
-
className="m-0 inline-flex"
|
|
223
|
+
<AIModelDropdown
|
|
224
|
+
value={value}
|
|
213
225
|
placeholder={placeholder}
|
|
214
|
-
{
|
|
215
|
-
|
|
226
|
+
onSelect={selectModel}
|
|
227
|
+
triggerClassName="text-sm"
|
|
228
|
+
customDropdownContent={
|
|
229
|
+
<>
|
|
230
|
+
<DropdownMenuSeparator />
|
|
231
|
+
<p className="px-2 py-1.5 text-sm text-muted-secondary flex items-center gap-1">
|
|
232
|
+
Enter a custom model
|
|
233
|
+
<Tooltip content="Models should include the provider prefix, e.g. 'openai/gpt-4o'">
|
|
234
|
+
<InfoIcon className="h-3 w-3" />
|
|
235
|
+
</Tooltip>
|
|
236
|
+
</p>
|
|
237
|
+
<div className="px-2 py-1">
|
|
238
|
+
<Input
|
|
239
|
+
data-testid={testId}
|
|
240
|
+
className="w-full border-border shadow-none focus-visible:shadow-xs"
|
|
241
|
+
placeholder={placeholder}
|
|
242
|
+
{...field}
|
|
243
|
+
value={asStringOrUndefined(field.value)}
|
|
244
|
+
onKeyDown={Events.stopPropagation()}
|
|
245
|
+
/>
|
|
246
|
+
{value && (
|
|
247
|
+
<IncorrectModelId
|
|
248
|
+
value={value}
|
|
249
|
+
includeSuggestion={false}
|
|
250
|
+
/>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
</>
|
|
254
|
+
}
|
|
216
255
|
/>
|
|
217
256
|
</FormControl>
|
|
218
257
|
<FormMessage />
|
|
219
|
-
<IsOverridden userConfig={config} name={name} />
|
|
220
258
|
</FormItem>
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<div className="flex flex-col space-y-1">
|
|
263
|
+
{renderFormItem()}
|
|
264
|
+
<IsOverridden userConfig={config} name={name} />
|
|
265
|
+
<IncorrectModelId value={value} />
|
|
266
|
+
{description && <FormDescription>{description}</FormDescription>}
|
|
267
|
+
</div>
|
|
268
|
+
);
|
|
269
|
+
}}
|
|
232
270
|
/>
|
|
233
271
|
);
|
|
234
272
|
};
|
|
@@ -297,6 +335,7 @@ export const ProviderSelect: React.FC<ProviderSelectProps> = ({
|
|
|
297
335
|
|
|
298
336
|
const renderCopilotProvider = (
|
|
299
337
|
form: UseFormReturn<UserConfig>,
|
|
338
|
+
onSubmit: (values: UserConfig) => Promise<void>,
|
|
300
339
|
config: UserConfig,
|
|
301
340
|
) => {
|
|
302
341
|
const copilot = form.getValues("completion.copilot");
|
|
@@ -331,27 +370,16 @@ const renderCopilotProvider = (
|
|
|
331
370
|
|
|
332
371
|
if (copilot === "custom") {
|
|
333
372
|
return (
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
placeholder="ollama/qwen2.5-coder:1.5b"
|
|
345
|
-
testId="custom-model-input"
|
|
346
|
-
description={
|
|
347
|
-
<>
|
|
348
|
-
Model to use for code completion when using a custom provider.
|
|
349
|
-
Models should include the provider name and model name separated
|
|
350
|
-
by a slash.
|
|
351
|
-
</>
|
|
352
|
-
}
|
|
353
|
-
/>
|
|
354
|
-
</>
|
|
373
|
+
<ModelSelector
|
|
374
|
+
label="Autocomplete Model"
|
|
375
|
+
form={form}
|
|
376
|
+
config={config}
|
|
377
|
+
name="ai.models.autocomplete_model"
|
|
378
|
+
placeholder="ollama/qwen2.5-coder:1.5b"
|
|
379
|
+
testId="custom-model-input"
|
|
380
|
+
description="Model to use for code completion when using a custom provider."
|
|
381
|
+
onSubmit={onSubmit}
|
|
382
|
+
/>
|
|
355
383
|
);
|
|
356
384
|
}
|
|
357
385
|
};
|
|
@@ -363,6 +391,7 @@ const SettingGroup = ({ children }: { children: React.ReactNode }) => {
|
|
|
363
391
|
export const AiCodeCompletionConfig: React.FC<AiConfigProps> = ({
|
|
364
392
|
form,
|
|
365
393
|
config,
|
|
394
|
+
onSubmit,
|
|
366
395
|
}) => {
|
|
367
396
|
return (
|
|
368
397
|
<SettingGroup>
|
|
@@ -380,7 +409,7 @@ export const AiCodeCompletionConfig: React.FC<AiConfigProps> = ({
|
|
|
380
409
|
testId="copilot-select"
|
|
381
410
|
/>
|
|
382
411
|
|
|
383
|
-
{renderCopilotProvider(form, config)}
|
|
412
|
+
{renderCopilotProvider(form, onSubmit, config)}
|
|
384
413
|
</SettingGroup>
|
|
385
414
|
);
|
|
386
415
|
};
|
|
@@ -645,17 +674,17 @@ export const AiProvidersConfig: React.FC<AiConfigProps> = ({
|
|
|
645
674
|
);
|
|
646
675
|
};
|
|
647
676
|
|
|
648
|
-
export const AiAssistConfig: React.FC<AiConfigProps> = ({
|
|
677
|
+
export const AiAssistConfig: React.FC<AiConfigProps> = ({
|
|
678
|
+
form,
|
|
679
|
+
config,
|
|
680
|
+
onSubmit,
|
|
681
|
+
}) => {
|
|
649
682
|
const isWasmRuntime = isWasm();
|
|
650
683
|
|
|
651
684
|
return (
|
|
652
685
|
<SettingGroup>
|
|
653
686
|
<SettingSubtitle>AI Assistant</SettingSubtitle>
|
|
654
|
-
<
|
|
655
|
-
Use the Chat panel to talk to your codebase, or make edits using the{" "}
|
|
656
|
-
<Kbd className="inline">Generate with AI</Kbd> button.
|
|
657
|
-
</p>
|
|
658
|
-
|
|
687
|
+
<FormErrorsBanner />
|
|
659
688
|
<ModelSelector
|
|
660
689
|
label="Chat Model"
|
|
661
690
|
form={form}
|
|
@@ -665,21 +694,10 @@ export const AiAssistConfig: React.FC<AiConfigProps> = ({ form, config }) => {
|
|
|
665
694
|
testId="ai-chat-model-input"
|
|
666
695
|
disabled={isWasmRuntime}
|
|
667
696
|
description={
|
|
668
|
-
|
|
669
|
-
<p>
|
|
670
|
-
Model to use for chat conversations in the Chat panel. Models
|
|
671
|
-
should include the provider name and model name separated by a
|
|
672
|
-
slash. For example, "anthropic/claude-3-5-sonnet-latest" or
|
|
673
|
-
"google/gemini-2.0-flash-exp".
|
|
674
|
-
</p>
|
|
675
|
-
<p className="pt-1">
|
|
676
|
-
Depending on the provider, we will use the respective API key and
|
|
677
|
-
additional configuration.
|
|
678
|
-
</p>
|
|
679
|
-
</>
|
|
697
|
+
<span>Model to use for chat conversations in the Chat panel.</span>
|
|
680
698
|
}
|
|
699
|
+
onSubmit={onSubmit}
|
|
681
700
|
/>
|
|
682
|
-
|
|
683
701
|
<ModelSelector
|
|
684
702
|
label="Edit Model"
|
|
685
703
|
form={form}
|
|
@@ -689,20 +707,26 @@ export const AiAssistConfig: React.FC<AiConfigProps> = ({ form, config }) => {
|
|
|
689
707
|
testId="ai-edit-model-input"
|
|
690
708
|
disabled={isWasmRuntime}
|
|
691
709
|
description={
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
should include the provider name and model name separated by a
|
|
697
|
-
slash.
|
|
698
|
-
</p>
|
|
699
|
-
<p className="pt-1">
|
|
700
|
-
You can use a faster, cheaper model for edits if desired.
|
|
701
|
-
</p>
|
|
702
|
-
</>
|
|
710
|
+
<span>
|
|
711
|
+
Model to use for code editing with the{" "}
|
|
712
|
+
<Kbd className="inline">Generate with AI</Kbd> button.
|
|
713
|
+
</span>
|
|
703
714
|
}
|
|
715
|
+
onSubmit={onSubmit}
|
|
704
716
|
/>
|
|
705
717
|
|
|
718
|
+
<ul className="bg-muted p-2 rounded-md list-disc space-y-1 pl-6">
|
|
719
|
+
<li className="text-xs text-muted-secondary">
|
|
720
|
+
Models should include the provider name and model name separated by a
|
|
721
|
+
slash. For example, "anthropic/claude-3-5-sonnet-latest" or
|
|
722
|
+
"google/gemini-2.0-flash-exp"
|
|
723
|
+
</li>
|
|
724
|
+
<li className="text-xs text-muted-secondary">
|
|
725
|
+
Depending on the provider, we will use the respective API key and
|
|
726
|
+
additional configuration.
|
|
727
|
+
</li>
|
|
728
|
+
</ul>
|
|
729
|
+
|
|
706
730
|
<FormField
|
|
707
731
|
control={form.control}
|
|
708
732
|
name="ai.rules"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useAtom } from "jotai";
|
|
4
4
|
import { SettingsIcon } from "lucide-react";
|
|
5
|
+
import { VisuallyHidden } from "react-aria";
|
|
5
6
|
import { AppConfigForm } from "@/components/app-config/app-config-form";
|
|
6
7
|
import {
|
|
7
8
|
Popover,
|
|
@@ -10,7 +11,12 @@ import {
|
|
|
10
11
|
} from "@/components/ui/popover";
|
|
11
12
|
import { Button as EditorButton } from "../editor/inputs/Inputs";
|
|
12
13
|
import { Button } from "../ui/button";
|
|
13
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
Dialog,
|
|
16
|
+
DialogContent,
|
|
17
|
+
DialogTitle,
|
|
18
|
+
DialogTrigger,
|
|
19
|
+
} from "../ui/dialog";
|
|
14
20
|
import { Tooltip } from "../ui/tooltip";
|
|
15
21
|
import { settingDialogAtom } from "./state";
|
|
16
22
|
import { UserConfigForm } from "./user-config-form";
|
|
@@ -46,6 +52,9 @@ export const ConfigButton: React.FC<Props> = ({
|
|
|
46
52
|
|
|
47
53
|
const userSettingsDialog = (
|
|
48
54
|
<DialogContent className="w-[80vw] h-[70vh] overflow-hidden sm:max-w-5xl top-[15vh] p-0">
|
|
55
|
+
<VisuallyHidden>
|
|
56
|
+
<DialogTitle>User settings</DialogTitle>
|
|
57
|
+
</VisuallyHidden>
|
|
49
58
|
<UserConfigForm />
|
|
50
59
|
</DialogContent>
|
|
51
60
|
);
|
|
@@ -1,38 +1,4 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
-
export const KNOWN_AI_MODELS = [
|
|
3
|
-
// Anthropic
|
|
4
|
-
"anthropic/claude-opus-4-1-20250805",
|
|
5
|
-
"anthropic/claude-opus-4-20250514",
|
|
6
|
-
"anthropic/claude-sonnet-4-20250514",
|
|
7
|
-
"anthropic/claude-3-7-sonnet-latest",
|
|
8
|
-
"anthropic/claude-3-5-sonnet-latest",
|
|
9
|
-
"anthropic/claude-3-5-haiku-latest",
|
|
10
|
-
|
|
11
|
-
// DeepSeek
|
|
12
|
-
"deepseek/deepseek-v3",
|
|
13
|
-
"deepseek/deepseek-r1",
|
|
14
|
-
|
|
15
|
-
// Google
|
|
16
|
-
"google/gemini-2.5-flash-preview-05-20",
|
|
17
|
-
"google/gemini-2.5-pro-preview-06-05",
|
|
18
|
-
"google/gemini-2.0-flash",
|
|
19
|
-
"google/gemini-2.0-flash-lite",
|
|
20
|
-
|
|
21
|
-
// OpenAI
|
|
22
|
-
"openai/o3",
|
|
23
|
-
"openai/o4-mini",
|
|
24
|
-
"openai/gpt-4.5-preview",
|
|
25
|
-
"openai/gpt-4.1",
|
|
26
|
-
"openai/gpt-4o",
|
|
27
|
-
"openai/gpt-3.5-turbo",
|
|
28
|
-
|
|
29
|
-
// AWS Bedrock Models
|
|
30
|
-
"bedrock/anthropic.claude-3-5-haiku-20241022-v1:0",
|
|
31
|
-
"bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0",
|
|
32
|
-
"bedrock/anthropic.claude-3-7-sonnet-20250219-v1:0",
|
|
33
|
-
"bedrock/meta.llama3-3-70b-instruct-v1:0",
|
|
34
|
-
"bedrock/cohere.command-r-plus-v1",
|
|
35
|
-
] as const;
|
|
36
2
|
|
|
37
3
|
/**
|
|
38
4
|
* AWS regions where the Bedrock service is available
|