@marimo-team/frontend 0.15.1-dev38 → 0.15.1
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-D8km1NYJ.js → ConnectedDataExplorerComponent-BmuPnAsd.js} +1 -1
- package/dist/assets/{ImageComparisonComponent-CERR01UB.js → ImageComparisonComponent-uf-9TFG8.js} +1 -1
- package/dist/assets/{VegaLite-DzZIYwlo.js → VegaLite-CQyOAWzz.js} +1 -1
- package/dist/assets/{_baseEach-DO-XcSIV.js → _baseEach-2yxz4QPK.js} +1 -1
- package/dist/assets/_baseMap-Uhjhm4ni.js +1 -0
- package/dist/assets/{_baseUniq-urG2vBXB.js → _baseUniq-kkBJp3jc.js} +1 -1
- package/dist/assets/{_createAggregator-DBxX1xI5.js → _createAggregator-ByV0zOLy.js} +1 -1
- package/dist/assets/{any-language-editor-BhBzti0Q.js → any-language-editor-B-cvHGmQ.js} +1 -1
- package/dist/assets/{architectureDiagram-KFL7JDKH-mgZE9P9E.js → architectureDiagram-KFL7JDKH-CrRiJlSM.js} +1 -1
- package/dist/assets/{blockDiagram-ZYB65J3Q-DhlvqBBN.js → blockDiagram-ZYB65J3Q-BARtxPtR.js} +1 -1
- package/dist/assets/{c4Diagram-AAMF2YG6-WgF7TmcX.js → c4Diagram-AAMF2YG6-C1rZ_aGX.js} +1 -1
- package/dist/assets/channel-BFLyOCHP.js +1 -0
- package/dist/assets/{chunk-ANTBXLJU-Cc0FLBv4.js → chunk-ANTBXLJU-CXz5OLFO.js} +1 -1
- package/dist/assets/{chunk-FHKO5MBM-TDVJkINs.js → chunk-FHKO5MBM-BIrdB1fE.js} +1 -1
- package/dist/assets/{chunk-GLLZNHP4-BFZQsgmM.js → chunk-GLLZNHP4-jO0hO6S-.js} +1 -1
- package/dist/assets/{chunk-JBRWN2VN-jewL7EcA.js → chunk-JBRWN2VN-CqtMDVwa.js} +1 -1
- package/dist/assets/{chunk-LXBSTHXV-DtMvensd.js → chunk-LXBSTHXV-68wVSxsz.js} +1 -1
- package/dist/assets/{chunk-NRVI72HA-B7LxIi5N.js → chunk-NRVI72HA-6jfSceAv.js} +1 -1
- package/dist/assets/{chunk-OMD6QJNC-D77lwHsv.js → chunk-OMD6QJNC-DItPiESC.js} +1 -1
- package/dist/assets/{chunk-WVR4S24B-BdzaJi_f.js → chunk-WVR4S24B-Dk_4BMrf.js} +1 -1
- package/dist/assets/{circle-play-DbLJhgWd.js → circle-play-DlcxXne6.js} +1 -1
- package/dist/assets/classDiagram-3BZAVTQC-DmaMTSt3.js +1 -0
- package/dist/assets/classDiagram-v2-QTMF73CY-DmaMTSt3.js +1 -0
- package/dist/assets/clone-D8_RSzqX.js +1 -0
- package/dist/assets/{compile-ittiAa-p.js → compile-rRtwsAuM.js} +1 -1
- package/dist/assets/{dagre-2BBEFEWP-woDg8Awj.js → dagre-2BBEFEWP-BRQiUAjN.js} +1 -1
- package/dist/assets/{data-grid-overlay-editor-ncLMoVIb.js → data-grid-overlay-editor-D69G4ntu.js} +1 -1
- package/dist/assets/{diagram-4IRLE6MV-PAN7AATi.js → diagram-4IRLE6MV-dbf1VDp2.js} +1 -1
- package/dist/assets/{diagram-GUPCWM2R-Ce2AHqMP.js → diagram-GUPCWM2R-03cBW_UX.js} +1 -1
- package/dist/assets/{diagram-RP2FKANI-2WiKea8D.js → diagram-RP2FKANI-izLn5JA6.js} +1 -1
- package/dist/assets/edit-page-KQGIuOho.css +1 -0
- package/dist/assets/{edit-page-CZn2bhY3.js → edit-page-zgOEBr1X.js} +48 -48
- package/dist/assets/{erDiagram-HZWUO2LU-D5yTZwmt.js → erDiagram-HZWUO2LU-DvzfndiW.js} +1 -1
- package/dist/assets/{flowDiagram-THRYKUMA-CgN86sZ8.js → flowDiagram-THRYKUMA-DvMVXvqb.js} +1 -1
- package/dist/assets/{ganttDiagram-WV7ZQ7D5-5skQ2H3l.js → ganttDiagram-WV7ZQ7D5-BzN8gykN.js} +1 -1
- package/dist/assets/{gitGraphDiagram-OJR772UL-C75De8fN.js → gitGraphDiagram-OJR772UL-wxIdjcsZ.js} +1 -1
- package/dist/assets/{glide-data-editor-x0d0Up5U.js → glide-data-editor-D6Yhpu80.js} +4 -4
- package/dist/assets/{graph-Die-6Bfl.js → graph-DLqrcAlu.js} +1 -1
- package/dist/assets/{home-page-XrEKfDMd.js → home-page-CDKRxIoa.js} +1 -1
- package/dist/assets/{index-D_ebOE-e.js → index-0xwlfUxT.js} +1 -1
- package/dist/assets/{index-CXwX4pUI.js → index-BB71mG_d.js} +182 -182
- package/dist/assets/{index-BfqMljbw.js → index-BZeyS4ec.js} +1 -1
- package/dist/assets/{index-CoAopzxc.js → index-Bs6OAQYb.js} +1 -1
- package/dist/assets/{index-Dlrab0dK.js → index-BsnxGFdv.js} +1 -1
- package/dist/assets/{index-Dh8C39yO.js → index-C0OGBvcf.js} +1 -1
- package/dist/assets/{index-BSJet7Zq.js → index-CK28dYOC.js} +1 -1
- package/dist/assets/{index-DmHX1Rlv.js → index-CQZ2Knp2.js} +1 -1
- package/dist/assets/{index-De7juNGF.js → index-Cdz_z5nt.js} +1 -1
- package/dist/assets/{index-BWBg8Lhk.js → index-ClD_znk5.js} +1 -1
- package/dist/assets/{index-kraNHKP1.js → index-D-b5sgmz.js} +1 -1
- package/dist/assets/{index-CCZkMG5j.js → index-D2UJqE4Q.js} +1 -1
- package/dist/assets/{index-v546YXgW.js → index-D2fV3sDg.js} +1 -1
- package/dist/assets/{index-UAPuSZUk.js → index-DUN68f-4.js} +1 -1
- package/dist/assets/{index-B3-zxsj0.js → index-DZKpeoa1.js} +1 -1
- package/dist/assets/{index-F531CohR.js → index-DgoUxLCo.js} +1 -1
- package/dist/assets/{index-B1-fG89N.js → index-Dh_3Mb5d.js} +1 -1
- package/dist/assets/{index-D9ot8WPl.js → index-DroyKRLH.js} +1 -1
- package/dist/assets/index-DryPQDfA.css +1 -0
- package/dist/assets/{index-jwhWh6uS.js → index-bUE-XbFV.js} +1 -1
- package/dist/assets/{index-B7JwzNrx.js → index-yz44yADt.js} +1 -1
- package/dist/assets/infoDiagram-6WOFNB3A-CvNjv0rh.js +2 -0
- package/dist/assets/{journeyDiagram-FFXJYRFH-CrWxkiXG.js → journeyDiagram-FFXJYRFH-BON6WB5Z.js} +1 -1
- package/dist/assets/{kanban-definition-KOZQBZVT-D5haTqpz.js → kanban-definition-KOZQBZVT-DENjuTrn.js} +1 -1
- package/dist/assets/{layout-BEc7rmra.js → layout-3QJH3hkO.js} +1 -1
- package/dist/assets/{linear-BLmy5c_P.js → linear-BFvzcy10.js} +1 -1
- package/dist/assets/links-B7kV7dQW.js +17 -0
- package/dist/assets/{mermaid-Bu2fp8N3.js → mermaid-0ClPHFIM.js} +4 -4
- package/dist/assets/{min-BF5a70Ha.js → min-CO20nzic.js} +1 -1
- package/dist/assets/{mindmap-definition-LNHGMQRG-jD2ylGBG.js → mindmap-definition-LNHGMQRG-0ssdD2sg.js} +1 -1
- package/dist/assets/{number-overlay-editor-BadGHMrr.js → number-overlay-editor-CVVXgCAp.js} +1 -1
- package/dist/assets/{pieDiagram-DBDJKBY4-B11UeqxS.js → pieDiagram-DBDJKBY4-DI0JNhUn.js} +1 -1
- package/dist/assets/{quadrantDiagram-YPSRARAO-DznrQXs-.js → quadrantDiagram-YPSRARAO-Bqg0IAoq.js} +1 -1
- package/dist/assets/{react-plotly-B6W1vV_l.js → react-plotly-yplDi2oo.js} +1 -1
- package/dist/assets/{requirementDiagram-EGVEC5DT-BuWp2ov9.js → requirementDiagram-EGVEC5DT-C_YIxDr_.js} +1 -1
- package/dist/assets/{run-page-BD4YehOu.js → run-page-Bv2C3uJ1.js} +1 -1
- package/dist/assets/{sankeyDiagram-HRAUVNP4-BmDCzHkG.js → sankeyDiagram-HRAUVNP4-Dm-w1V71.js} +1 -1
- package/dist/assets/{sequenceDiagram-WFGC7UMF-B6aCHwAd.js → sequenceDiagram-WFGC7UMF-uox9Aix1.js} +1 -1
- package/dist/assets/{slides-component-CkHAJhvv.js → slides-component-BBkS68D5.js} +1 -1
- package/dist/assets/{sortBy-BgSF6Z3p.js → sortBy-yoFvjOas.js} +1 -1
- package/dist/assets/{stateDiagram-UUKSUZ4H-DcGKoXUB.js → stateDiagram-UUKSUZ4H-DVHFynm4.js} +1 -1
- package/dist/assets/stateDiagram-v2-EYPG3UTE-_z_RwTAL.js +1 -0
- package/dist/assets/{storage-BX5GfYeC.js → storage-C__Jd9kD.js} +3 -3
- package/dist/assets/{terminal-CpmTRzDD.js → terminal-BpOKN-Gr.js} +1 -1
- package/dist/assets/{time-igUPFhda.js → time-q7el0-6s.js} +1 -1
- package/dist/assets/{timeline-definition-3HZDQTIS-7b4K28uW.js → timeline-definition-3HZDQTIS-K-bheyBf.js} +1 -1
- package/dist/assets/{tracing-BWeRyJmf.js → tracing-Cv9nIL_Y.js} +2 -2
- package/dist/assets/{trash-Bv9cD3VE.js → trash-Izx1PgIJ.js} +1 -1
- package/dist/assets/{treemap-75Q7IDZK-DPkOPTcz.js → treemap-75Q7IDZK-mGt3Vxl-.js} +1 -1
- package/dist/assets/{vega-component-CGVDFtwG.js → vega-component-B1UZ_5Pg.js} +1 -1
- package/dist/assets/{xychartDiagram-FDP5SA34-BbvKlNzf.js → xychartDiagram-FDP5SA34-DGx9I_mu.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +7 -7
- package/src/components/ai/ai-model-dropdown.tsx +3 -3
- package/src/components/app-config/ai-config.tsx +244 -8
- package/src/components/chat/markdown-renderer.css +15 -0
- package/src/components/chat/markdown-renderer.tsx +2 -1
- package/src/components/editor/ai/completion-utils.ts +6 -2
- package/src/components/editor/cell/cell-context-menu.tsx +35 -13
- package/src/core/ai/__tests__/model-registry.test.ts +28 -0
- package/src/core/ai/model-registry.ts +44 -8
- package/src/core/codemirror/language/languages/sql/utils.ts +6 -1
- package/src/plugins/impl/FileBrowserPlugin.tsx +14 -20
- package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +19 -2
- package/src/plugins/impl/common/error-banner.tsx +3 -0
- package/dist/assets/_baseMap-Dg1mkEJY.js +0 -1
- package/dist/assets/channel-D1aHTD78.js +0 -1
- package/dist/assets/classDiagram-3BZAVTQC-C8Rjm-tQ.js +0 -1
- package/dist/assets/classDiagram-v2-QTMF73CY-C8Rjm-tQ.js +0 -1
- package/dist/assets/clone-BWlvmMhq.js +0 -1
- package/dist/assets/edit-page-jgH_jXgs.css +0 -1
- package/dist/assets/index-B6q_Qknx.css +0 -1
- package/dist/assets/infoDiagram-6WOFNB3A-mJ455ogl.js +0 -2
- package/dist/assets/links-BGVSWGyv.js +0 -17
- package/dist/assets/stateDiagram-v2-EYPG3UTE-BW5Lhcrb.js +0 -1
@@ -1,7 +1,14 @@
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
2
2
|
|
3
|
-
import {
|
4
|
-
|
3
|
+
import {
|
4
|
+
BotIcon,
|
5
|
+
BrainIcon,
|
6
|
+
ChevronRightIcon,
|
7
|
+
InfoIcon,
|
8
|
+
PlusIcon,
|
9
|
+
Trash2Icon,
|
10
|
+
} from "lucide-react";
|
11
|
+
import React, { useId, useMemo, useState } from "react";
|
5
12
|
import {
|
6
13
|
Button as AriaButton,
|
7
14
|
Tree,
|
@@ -26,8 +33,10 @@ import { NativeSelect } from "@/components/ui/native-select";
|
|
26
33
|
import { Textarea } from "@/components/ui/textarea";
|
27
34
|
import {
|
28
35
|
AiModelId,
|
36
|
+
PROVIDERS,
|
29
37
|
type ProviderId,
|
30
38
|
type QualifiedModelId,
|
39
|
+
type ShortModelId,
|
31
40
|
} from "@/core/ai/ids/ids";
|
32
41
|
import { type AiModel, AiModelRegistry } from "@/core/ai/model-registry";
|
33
42
|
import { CopilotConfig } from "@/core/codemirror/copilot/copilot-config";
|
@@ -36,7 +45,7 @@ import { isWasm } from "@/core/wasm/utils";
|
|
36
45
|
import { cn } from "@/utils/cn";
|
37
46
|
import { Events } from "@/utils/events";
|
38
47
|
import { Strings } from "@/utils/strings";
|
39
|
-
import { AIModelDropdown } from "../ai/ai-model-dropdown";
|
48
|
+
import { AIModelDropdown, getProviderLabel } from "../ai/ai-model-dropdown";
|
40
49
|
import {
|
41
50
|
AiProviderIcon,
|
42
51
|
type AiProviderIconProps,
|
@@ -48,9 +57,18 @@ import {
|
|
48
57
|
AccordionItem,
|
49
58
|
AccordionTrigger,
|
50
59
|
} from "../ui/accordion";
|
60
|
+
import { Button } from "../ui/button";
|
51
61
|
import { Checkbox } from "../ui/checkbox";
|
52
62
|
import { DropdownMenuSeparator } from "../ui/dropdown-menu";
|
63
|
+
import { Label } from "../ui/label";
|
53
64
|
import { ExternalLink } from "../ui/links";
|
65
|
+
import {
|
66
|
+
Select,
|
67
|
+
SelectContent,
|
68
|
+
SelectGroup,
|
69
|
+
SelectItem,
|
70
|
+
SelectTrigger,
|
71
|
+
} from "../ui/select";
|
54
72
|
import { Switch } from "../ui/switch";
|
55
73
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
56
74
|
import { Tooltip } from "../ui/tooltip";
|
@@ -424,6 +442,7 @@ interface ModelListItemProps {
|
|
424
442
|
model: AiModel;
|
425
443
|
isEnabled: boolean;
|
426
444
|
onToggle: (modelId: QualifiedModelId) => void;
|
445
|
+
onDelete: (modelId: QualifiedModelId) => void;
|
427
446
|
}
|
428
447
|
|
429
448
|
const ModelListItem: React.FC<ModelListItemProps> = ({
|
@@ -431,11 +450,18 @@ const ModelListItem: React.FC<ModelListItemProps> = ({
|
|
431
450
|
model,
|
432
451
|
isEnabled,
|
433
452
|
onToggle,
|
453
|
+
onDelete,
|
434
454
|
}) => {
|
435
455
|
const handleToggle = () => {
|
436
456
|
onToggle(qualifiedId);
|
437
457
|
};
|
438
458
|
|
459
|
+
const handleDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
|
460
|
+
e.stopPropagation();
|
461
|
+
e.preventDefault();
|
462
|
+
onDelete(qualifiedId);
|
463
|
+
};
|
464
|
+
|
439
465
|
return (
|
440
466
|
<TreeItem
|
441
467
|
id={qualifiedId}
|
@@ -446,6 +472,16 @@ const ModelListItem: React.FC<ModelListItemProps> = ({
|
|
446
472
|
<TreeItemContent>
|
447
473
|
<div className="flex items-center justify-between px-4 py-3 border-b last:border-b-0 cursor-pointer outline-none">
|
448
474
|
<ModelInfoCard model={model} qualifiedId={qualifiedId} />
|
475
|
+
{model.custom && (
|
476
|
+
<Button
|
477
|
+
variant="ghost"
|
478
|
+
size="icon"
|
479
|
+
onClick={handleDelete}
|
480
|
+
className="mr-2 hover:bg-transparent"
|
481
|
+
>
|
482
|
+
<Trash2Icon className="h-3.5 w-3.5 text-muted-foreground" />
|
483
|
+
</Button>
|
484
|
+
)}
|
449
485
|
<Switch checked={isEnabled} onClick={handleToggle} size="sm" />
|
450
486
|
</div>
|
451
487
|
</TreeItemContent>
|
@@ -465,6 +501,9 @@ const ModelInfoCard = ({
|
|
465
501
|
<div className="flex flex-col flex-1">
|
466
502
|
<div className="flex items-center gap-2">
|
467
503
|
<h3 className="font-medium">{model.name}</h3>
|
504
|
+
<Tooltip content="Custom model">
|
505
|
+
{model.custom && <BotIcon className="h-4 w-4" />}
|
506
|
+
</Tooltip>
|
468
507
|
</div>
|
469
508
|
<span className="text-xs text-muted-foreground font-mono">
|
470
509
|
{qualifiedId}
|
@@ -932,6 +971,7 @@ interface ProviderTreeItemProps {
|
|
932
971
|
enabledModels: Set<QualifiedModelId>;
|
933
972
|
onToggleModel: (modelId: QualifiedModelId) => void;
|
934
973
|
onToggleProvider: (providerId: ProviderId, enable: boolean) => void;
|
974
|
+
onDeleteModel: (modelId: QualifiedModelId) => void;
|
935
975
|
}
|
936
976
|
|
937
977
|
const ProviderTreeItem: React.FC<ProviderTreeItemProps> = ({
|
@@ -940,6 +980,7 @@ const ProviderTreeItem: React.FC<ProviderTreeItemProps> = ({
|
|
940
980
|
enabledModels,
|
941
981
|
onToggleModel,
|
942
982
|
onToggleProvider,
|
983
|
+
onDeleteModel,
|
943
984
|
}) => {
|
944
985
|
const enabledCount = models.filter((model) =>
|
945
986
|
enabledModels.has(new AiModelId(providerId, model.model).id),
|
@@ -996,6 +1037,7 @@ const ProviderTreeItem: React.FC<ProviderTreeItemProps> = ({
|
|
996
1037
|
model={model}
|
997
1038
|
isEnabled={enabledModels.has(qualifiedId)}
|
998
1039
|
onToggle={onToggleModel}
|
1040
|
+
onDelete={onDeleteModel}
|
999
1041
|
/>
|
1000
1042
|
);
|
1001
1043
|
})}
|
@@ -1007,13 +1049,18 @@ export const AiModelDisplayConfig: React.FC<AiConfigProps> = ({
|
|
1007
1049
|
form,
|
1008
1050
|
onSubmit,
|
1009
1051
|
}) => {
|
1052
|
+
const customModels = useWatch({
|
1053
|
+
control: form.control,
|
1054
|
+
name: "ai.models.custom_models",
|
1055
|
+
}) as QualifiedModelId[];
|
1056
|
+
|
1010
1057
|
const aiModelRegistry = useMemo(
|
1011
1058
|
() =>
|
1012
1059
|
AiModelRegistry.create({
|
1013
1060
|
displayedModels: [],
|
1014
|
-
customModels:
|
1061
|
+
customModels: customModels,
|
1015
1062
|
}),
|
1016
|
-
[],
|
1063
|
+
[customModels],
|
1017
1064
|
);
|
1018
1065
|
const currentDisplayedModels = useWatch({
|
1019
1066
|
control: form.control,
|
@@ -1022,6 +1069,7 @@ export const AiModelDisplayConfig: React.FC<AiConfigProps> = ({
|
|
1022
1069
|
}) as QualifiedModelId[];
|
1023
1070
|
const currentDisplayedModelsSet = new Set(currentDisplayedModels);
|
1024
1071
|
const modelsByProvider = aiModelRegistry.getGroupedModelsByProvider();
|
1072
|
+
const listModelsByProvider = aiModelRegistry.getListModelsByProvider();
|
1025
1073
|
|
1026
1074
|
const toggleModelDisplay = useEvent((modelId: QualifiedModelId) => {
|
1027
1075
|
const newModels = currentDisplayedModelsSet.has(modelId)
|
@@ -1050,9 +1098,15 @@ export const AiModelDisplayConfig: React.FC<AiConfigProps> = ({
|
|
1050
1098
|
},
|
1051
1099
|
);
|
1052
1100
|
|
1101
|
+
const deleteModel = useEvent((modelId: QualifiedModelId) => {
|
1102
|
+
const newModels = customModels.filter((id) => id !== modelId);
|
1103
|
+
form.setValue("ai.models.custom_models", newModels);
|
1104
|
+
onSubmit(form.getValues());
|
1105
|
+
});
|
1106
|
+
|
1053
1107
|
return (
|
1054
|
-
<SettingGroup>
|
1055
|
-
<p className="text-sm text-muted-secondary mb-
|
1108
|
+
<SettingGroup className="gap-2">
|
1109
|
+
<p className="text-sm text-muted-secondary mb-6">
|
1056
1110
|
Control which AI models are displayed in model selection dropdowns. When
|
1057
1111
|
no models are selected, all available models will be shown.
|
1058
1112
|
</p>
|
@@ -1063,7 +1117,7 @@ export const AiModelDisplayConfig: React.FC<AiConfigProps> = ({
|
|
1063
1117
|
className="flex-1 overflow-auto outline-none focus-visible:outline-none"
|
1064
1118
|
selectionMode="none"
|
1065
1119
|
>
|
1066
|
-
{
|
1120
|
+
{listModelsByProvider.map(([providerId, models]) => (
|
1067
1121
|
<ProviderTreeItem
|
1068
1122
|
key={providerId}
|
1069
1123
|
providerId={providerId}
|
@@ -1071,14 +1125,196 @@ export const AiModelDisplayConfig: React.FC<AiConfigProps> = ({
|
|
1071
1125
|
enabledModels={currentDisplayedModelsSet}
|
1072
1126
|
onToggleModel={toggleModelDisplay}
|
1073
1127
|
onToggleProvider={toggleProviderModels}
|
1128
|
+
onDeleteModel={deleteModel}
|
1074
1129
|
/>
|
1075
1130
|
))}
|
1076
1131
|
</Tree>
|
1077
1132
|
</div>
|
1133
|
+
<AddModelForm form={form} customModels={customModels} />
|
1078
1134
|
</SettingGroup>
|
1079
1135
|
);
|
1080
1136
|
};
|
1081
1137
|
|
1138
|
+
export const AddModelForm: React.FC<{
|
1139
|
+
form: UseFormReturn<UserConfig>;
|
1140
|
+
customModels: QualifiedModelId[];
|
1141
|
+
}> = ({ form, customModels }) => {
|
1142
|
+
const [isFormOpen, setIsFormOpen] = useState(false);
|
1143
|
+
const [modelAdded, setModelAdded] = useState(false);
|
1144
|
+
const [provider, setProvider] = useState<ProviderId | "custom" | null>(null);
|
1145
|
+
const [customProviderName, setCustomProviderName] = useState("");
|
1146
|
+
const [modelName, setModelName] = useState("");
|
1147
|
+
|
1148
|
+
const providerSelectId = useId();
|
1149
|
+
const customProviderInputId = useId();
|
1150
|
+
const modelNameInputId = useId();
|
1151
|
+
|
1152
|
+
const isCustomProvider = provider === "custom";
|
1153
|
+
const providerName = isCustomProvider ? customProviderName : provider;
|
1154
|
+
const hasValidValues = providerName?.trim() && modelName?.trim();
|
1155
|
+
|
1156
|
+
const resetForm = () => {
|
1157
|
+
setProvider(null);
|
1158
|
+
setCustomProviderName("");
|
1159
|
+
setModelName("");
|
1160
|
+
setIsFormOpen(false);
|
1161
|
+
};
|
1162
|
+
|
1163
|
+
const handleAddModel = () => {
|
1164
|
+
if (!hasValidValues) {
|
1165
|
+
return;
|
1166
|
+
}
|
1167
|
+
|
1168
|
+
const newModel = new AiModelId(
|
1169
|
+
providerName as ProviderId,
|
1170
|
+
modelName as ShortModelId,
|
1171
|
+
);
|
1172
|
+
|
1173
|
+
form.setValue("ai.models.custom_models", [newModel.id, ...customModels]);
|
1174
|
+
resetForm();
|
1175
|
+
|
1176
|
+
// Show model added message for 2 seconds
|
1177
|
+
setModelAdded(true);
|
1178
|
+
setTimeout(() => setModelAdded(false), 2000);
|
1179
|
+
};
|
1180
|
+
|
1181
|
+
const providerClassName = "w-40 truncate";
|
1182
|
+
|
1183
|
+
const providerSelect = (
|
1184
|
+
<div className="flex flex-col gap-2">
|
1185
|
+
<div className="flex items-center gap-2">
|
1186
|
+
<Label
|
1187
|
+
htmlFor={providerSelectId}
|
1188
|
+
className="text-sm font-medium text-muted-foreground min-w-12"
|
1189
|
+
>
|
1190
|
+
Provider
|
1191
|
+
</Label>
|
1192
|
+
<Select
|
1193
|
+
value={provider || ""}
|
1194
|
+
onValueChange={(v) => setProvider(v as ProviderId | "custom")}
|
1195
|
+
>
|
1196
|
+
<SelectTrigger id={providerSelectId} className={providerClassName}>
|
1197
|
+
{provider ? (
|
1198
|
+
<div className="flex items-center gap-1.5">
|
1199
|
+
<AiProviderIcon
|
1200
|
+
provider={provider as ProviderId}
|
1201
|
+
className="h-3.5 w-3.5"
|
1202
|
+
/>
|
1203
|
+
<span>{getProviderLabel(provider as ProviderId)}</span>
|
1204
|
+
</div>
|
1205
|
+
) : (
|
1206
|
+
<span className="text-muted-foreground">Select...</span>
|
1207
|
+
)}
|
1208
|
+
</SelectTrigger>
|
1209
|
+
<SelectContent>
|
1210
|
+
<SelectGroup>
|
1211
|
+
<SelectItem value="custom">
|
1212
|
+
<div className="flex items-center gap-2">
|
1213
|
+
<AiProviderIcon
|
1214
|
+
provider="openai-compatible"
|
1215
|
+
className="h-4 w-4"
|
1216
|
+
/>
|
1217
|
+
<span>Custom</span>
|
1218
|
+
</div>
|
1219
|
+
</SelectItem>
|
1220
|
+
{PROVIDERS.filter((p) => p !== "marimo").map((p) => (
|
1221
|
+
<SelectItem key={p} value={p}>
|
1222
|
+
<div className="flex items-center gap-2">
|
1223
|
+
<AiProviderIcon provider={p} className="h-4 w-4" />
|
1224
|
+
<span>{getProviderLabel(p)}</span>
|
1225
|
+
</div>
|
1226
|
+
</SelectItem>
|
1227
|
+
))}
|
1228
|
+
</SelectGroup>
|
1229
|
+
</SelectContent>
|
1230
|
+
</Select>
|
1231
|
+
</div>
|
1232
|
+
|
1233
|
+
{isCustomProvider && (
|
1234
|
+
<div className="flex items-center gap-2">
|
1235
|
+
<Label
|
1236
|
+
htmlFor={customProviderInputId}
|
1237
|
+
className="text-sm font-medium text-muted-foreground min-w-12"
|
1238
|
+
>
|
1239
|
+
Name
|
1240
|
+
</Label>
|
1241
|
+
<Input
|
1242
|
+
id={customProviderInputId}
|
1243
|
+
value={customProviderName}
|
1244
|
+
onChange={(e) => setCustomProviderName(e.target.value)}
|
1245
|
+
placeholder="openrouter"
|
1246
|
+
className={providerClassName}
|
1247
|
+
/>
|
1248
|
+
</div>
|
1249
|
+
)}
|
1250
|
+
</div>
|
1251
|
+
);
|
1252
|
+
|
1253
|
+
const modelInput = (
|
1254
|
+
<div
|
1255
|
+
className={cn(
|
1256
|
+
"flex items-center gap-2",
|
1257
|
+
isCustomProvider && "self-start",
|
1258
|
+
)}
|
1259
|
+
>
|
1260
|
+
<Label
|
1261
|
+
htmlFor={modelNameInputId}
|
1262
|
+
className="text-sm font-medium text-muted-foreground"
|
1263
|
+
>
|
1264
|
+
Model
|
1265
|
+
</Label>
|
1266
|
+
<Input
|
1267
|
+
id={modelNameInputId}
|
1268
|
+
value={modelName}
|
1269
|
+
onChange={(e) => setModelName(e.target.value)}
|
1270
|
+
placeholder="gpt-4"
|
1271
|
+
className="text-xs mb-0"
|
1272
|
+
/>
|
1273
|
+
</div>
|
1274
|
+
);
|
1275
|
+
|
1276
|
+
const inputForm = (
|
1277
|
+
<div className="flex items-center gap-3 p-3 border border-border rounded-md">
|
1278
|
+
{providerSelect}
|
1279
|
+
{modelInput}
|
1280
|
+
<div
|
1281
|
+
className={cn("flex gap-1.5 ml-auto", isCustomProvider && "self-end")}
|
1282
|
+
>
|
1283
|
+
<Button onClick={handleAddModel} disabled={!hasValidValues} size="xs">
|
1284
|
+
Add
|
1285
|
+
</Button>
|
1286
|
+
<Button variant="outline" onClick={resetForm} size="xs">
|
1287
|
+
Cancel
|
1288
|
+
</Button>
|
1289
|
+
</div>
|
1290
|
+
</div>
|
1291
|
+
);
|
1292
|
+
|
1293
|
+
return (
|
1294
|
+
<div>
|
1295
|
+
{isFormOpen && inputForm}
|
1296
|
+
<div className="flex flex-row text-sm">
|
1297
|
+
<Button
|
1298
|
+
onClick={(e) => {
|
1299
|
+
e.preventDefault();
|
1300
|
+
setIsFormOpen(true);
|
1301
|
+
}}
|
1302
|
+
variant="link"
|
1303
|
+
disabled={isFormOpen}
|
1304
|
+
>
|
1305
|
+
<PlusIcon className="h-4 w-4 mr-2 mb-0.5" />
|
1306
|
+
Add Model
|
1307
|
+
</Button>
|
1308
|
+
{modelAdded && (
|
1309
|
+
<div className="flex items-center gap-1 text-green-700 bg-green-500/10 px-2 py-1 rounded-md ml-auto">
|
1310
|
+
✓ Model added
|
1311
|
+
</div>
|
1312
|
+
)}
|
1313
|
+
</div>
|
1314
|
+
</div>
|
1315
|
+
);
|
1316
|
+
};
|
1317
|
+
|
1082
1318
|
export const AiConfig: React.FC<AiConfigProps> = ({
|
1083
1319
|
form,
|
1084
1320
|
config,
|
@@ -17,6 +17,7 @@ import { autoInstantiateAtom } from "@/core/config/config";
|
|
17
17
|
import { LazyAnyLanguageCodeMirror } from "@/plugins/impl/code/LazyAnyLanguageCodeMirror";
|
18
18
|
import { useTheme } from "@/theme/useTheme";
|
19
19
|
import { copyToClipboard } from "@/utils/copy";
|
20
|
+
import "./markdown-renderer.css";
|
20
21
|
|
21
22
|
const extensions = [EditorView.lineWrapping];
|
22
23
|
|
@@ -202,7 +203,7 @@ const MemoizedMarkdownBlock = memo(
|
|
202
203
|
<Markdown
|
203
204
|
components={COMPONENTS}
|
204
205
|
remarkPlugins={PLUGINS}
|
205
|
-
className="prose dark:prose-invert max-w-none prose-pre:pl-0"
|
206
|
+
className="mo-markdown-renderer prose dark:prose-invert max-w-none prose-pre:pl-0"
|
206
207
|
>
|
207
208
|
{content}
|
208
209
|
</Markdown>
|
@@ -19,8 +19,12 @@ export function getAICompletionBody({
|
|
19
19
|
}: {
|
20
20
|
input: string;
|
21
21
|
}): Omit<AiCompletionRequest, "language" | "prompt" | "code"> {
|
22
|
-
|
23
|
-
|
22
|
+
let contextString = "";
|
23
|
+
// Skip if no '@' in the input
|
24
|
+
if (input.includes("@")) {
|
25
|
+
contextString = extractDatasetsAndVariables(input);
|
26
|
+
Logger.debug("Included context", contextString);
|
27
|
+
}
|
24
28
|
|
25
29
|
return {
|
26
30
|
includeOtherCode: getCodes(""),
|
@@ -17,6 +17,7 @@ import {
|
|
17
17
|
ContextMenuSeparator,
|
18
18
|
ContextMenuTrigger,
|
19
19
|
} from "@/components/ui/context-menu";
|
20
|
+
import { menuItemVariants } from "@/components/ui/menu-items";
|
20
21
|
import { Tooltip } from "@/components/ui/tooltip";
|
21
22
|
import { toast } from "@/components/ui/use-toast";
|
22
23
|
import { useCellData, useCellRuntime } from "@/core/cells/cells";
|
@@ -219,19 +220,40 @@ export const CellActionsContextMenu = ({
|
|
219
220
|
}
|
220
221
|
|
221
222
|
return (
|
222
|
-
<
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
223
|
+
<Fragment key={action.label}>
|
224
|
+
{
|
225
|
+
// Set disableClick items such as cell name input
|
226
|
+
// to div to prevent roving focus
|
227
|
+
action.disableClick ? (
|
228
|
+
<div
|
229
|
+
className={menuItemVariants({
|
230
|
+
className: action.disabled ? "opacity-50!" : "",
|
231
|
+
variant: action.variant,
|
232
|
+
})}
|
233
|
+
onKeyDown={(evt) => {
|
234
|
+
evt.stopPropagation();
|
235
|
+
}}
|
236
|
+
// Prevent keydown propagation, that focus does not jump to shortcut which start with same letter
|
237
|
+
// e.g. input "C", then focus jump to "Copy"
|
238
|
+
>
|
239
|
+
{body}
|
240
|
+
</div>
|
241
|
+
) : (
|
242
|
+
<ContextMenuItem
|
243
|
+
className={action.disabled ? "opacity-50!" : ""}
|
244
|
+
onSelect={(evt) => {
|
245
|
+
if (action.disableClick || action.disabled) {
|
246
|
+
return;
|
247
|
+
}
|
248
|
+
action.handle(evt);
|
249
|
+
}}
|
250
|
+
variant={action.variant}
|
251
|
+
>
|
252
|
+
{body}
|
253
|
+
</ContextMenuItem>
|
254
|
+
)
|
255
|
+
}
|
256
|
+
</Fragment>
|
235
257
|
);
|
236
258
|
})}
|
237
259
|
{i < allActions.length - 1 && <ContextMenuSeparator />}
|
@@ -234,6 +234,34 @@ describe("AiModelRegistry", () => {
|
|
234
234
|
});
|
235
235
|
});
|
236
236
|
|
237
|
+
describe("getListModelsByProvider", () => {
|
238
|
+
/**
|
239
|
+
* Provider sort order depends on `provider.json`. We can hardcode for tests
|
240
|
+
* OpenAI, Bedrock, Azure, Anthropic, Google, Ollama, GitHub, Marimo
|
241
|
+
*/
|
242
|
+
const PROVIDER_SORT_ORDER = ["openai", "anthropic", "google"];
|
243
|
+
|
244
|
+
it("should return list of models by provider", () => {
|
245
|
+
const registry = AiModelRegistry.create({});
|
246
|
+
const listModelsByProvider = registry.getListModelsByProvider();
|
247
|
+
expect(listModelsByProvider).toHaveLength(3);
|
248
|
+
|
249
|
+
// Should be sorted by provider
|
250
|
+
const providers = listModelsByProvider.map(([provider]) => provider);
|
251
|
+
expect(providers).toEqual(PROVIDER_SORT_ORDER);
|
252
|
+
});
|
253
|
+
|
254
|
+
it("should include custom providers at the top", () => {
|
255
|
+
const customModels = ["openrouter/custom-gpt"];
|
256
|
+
const registry = AiModelRegistry.create({ customModels });
|
257
|
+
const listModelsByProvider = registry.getListModelsByProvider();
|
258
|
+
expect(listModelsByProvider).toHaveLength(4);
|
259
|
+
|
260
|
+
const providers = listModelsByProvider.map(([provider]) => provider);
|
261
|
+
expect(providers).toEqual(["openrouter", ...PROVIDER_SORT_ORDER]);
|
262
|
+
});
|
263
|
+
});
|
264
|
+
|
237
265
|
describe("getCustomModels", () => {
|
238
266
|
it("should return empty set when no custom models", () => {
|
239
267
|
const registry = AiModelRegistry.create({});
|
@@ -13,6 +13,18 @@ import { once } from "@/utils/once";
|
|
13
13
|
import type { ProviderId } from "./ids/ids";
|
14
14
|
import { AiModelId, type QualifiedModelId, type ShortModelId } from "./ids/ids";
|
15
15
|
|
16
|
+
export const PROVIDER_SORT_ORDER: ProviderId[] = [
|
17
|
+
// Sort by popular ones
|
18
|
+
"anthropic",
|
19
|
+
"openai",
|
20
|
+
"google",
|
21
|
+
"github",
|
22
|
+
"deepseek",
|
23
|
+
"azure",
|
24
|
+
"bedrock",
|
25
|
+
"ollama",
|
26
|
+
];
|
27
|
+
|
16
28
|
export interface AiModel extends AiModelType {
|
17
29
|
roles: Role[];
|
18
30
|
model: ShortModelId;
|
@@ -41,13 +53,21 @@ const getKnownModelMap = once((): ReadonlyMap<QualifiedModelId, AiModel> => {
|
|
41
53
|
return modelMap;
|
42
54
|
});
|
43
55
|
|
44
|
-
const getProviderMap = once(
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
}
|
49
|
-
|
50
|
-
|
56
|
+
const getProviderMap = once(
|
57
|
+
(): {
|
58
|
+
providerMap: ReadonlyMap<ProviderId, AiProvider>;
|
59
|
+
providerToOrderIdx: ReadonlyMap<ProviderId, number>;
|
60
|
+
} => {
|
61
|
+
const providerMap = new Map<ProviderId, AiProvider>();
|
62
|
+
const providerToOrderIdx = new Map<ProviderId, number>();
|
63
|
+
providers.forEach((provider, idx) => {
|
64
|
+
const providerId = provider.id as ProviderId;
|
65
|
+
providerMap.set(providerId, provider);
|
66
|
+
providerToOrderIdx.set(providerId, idx);
|
67
|
+
});
|
68
|
+
return { providerMap, providerToOrderIdx };
|
69
|
+
},
|
70
|
+
);
|
51
71
|
|
52
72
|
export class AiModelRegistry {
|
53
73
|
private modelsByProviderMap = new MultiMap<ProviderId, AiModel>();
|
@@ -66,7 +86,8 @@ export class AiModelRegistry {
|
|
66
86
|
}
|
67
87
|
|
68
88
|
static getProviderInfo(providerId: ProviderId) {
|
69
|
-
|
89
|
+
const { providerMap } = getProviderMap();
|
90
|
+
return providerMap.get(providerId);
|
70
91
|
}
|
71
92
|
|
72
93
|
/**
|
@@ -180,6 +201,21 @@ export class AiModelRegistry {
|
|
180
201
|
return this.modelsByProviderMap;
|
181
202
|
}
|
182
203
|
|
204
|
+
getListModelsByProvider(): Array<[ProviderId, AiModel[]]> {
|
205
|
+
const modelsByProvider = this.getGroupedModelsByProvider();
|
206
|
+
const arrayModels = [...modelsByProvider.entries()];
|
207
|
+
const providerToOrderIdx = getProviderMap().providerToOrderIdx;
|
208
|
+
|
209
|
+
arrayModels.sort((a, b) => {
|
210
|
+
const aProvider = a[0];
|
211
|
+
const bProvider = b[0];
|
212
|
+
const aOrderIdx = providerToOrderIdx.get(aProvider) ?? 0;
|
213
|
+
const bOrderIdx = providerToOrderIdx.get(bProvider) ?? 0;
|
214
|
+
return aOrderIdx - bOrderIdx;
|
215
|
+
});
|
216
|
+
return arrayModels;
|
217
|
+
}
|
218
|
+
|
183
219
|
getModelsMap() {
|
184
220
|
return this.modelsMap;
|
185
221
|
}
|
@@ -12,7 +12,10 @@ import {
|
|
12
12
|
SQLite,
|
13
13
|
StandardSQL,
|
14
14
|
} from "@codemirror/lang-sql";
|
15
|
-
import {
|
15
|
+
import {
|
16
|
+
BigQueryDialect,
|
17
|
+
DuckDBDialect,
|
18
|
+
} from "@marimo-team/codemirror-sql/dialects";
|
16
19
|
import type { DataSourceConnection } from "@/core/kernel/messages";
|
17
20
|
|
18
21
|
export function guessDialect(
|
@@ -38,6 +41,8 @@ export function guessDialect(
|
|
38
41
|
case "oracledb":
|
39
42
|
case "oracle":
|
40
43
|
return PLSQL;
|
44
|
+
case "bigquery":
|
45
|
+
return BigQueryDialect;
|
41
46
|
default:
|
42
47
|
return undefined;
|
43
48
|
}
|