@marimo-team/frontend 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/assets/{ConnectedDataExplorerComponent-DzJwGXv8.js → ConnectedDataExplorerComponent-BKPX_vvE.js} +1 -1
- package/dist/assets/{ImageComparisonComponent-BfMq_aag.js → ImageComparisonComponent-wibseBhV.js} +1 -1
- package/dist/assets/{VegaLite-ChTTY5_9.js → VegaLite-DZxY-oPw.js} +1 -1
- package/dist/assets/{_baseEach-rPPiuvyD.js → _baseEach-5UkF3byo.js} +1 -1
- package/dist/assets/_baseMap-CcY33y3K.js +1 -0
- package/dist/assets/{_baseUniq-PFDhmjrU.js → _baseUniq-Ci_pYsBV.js} +1 -1
- package/dist/assets/{_createAggregator-Bbx1ZweT.js → _createAggregator-D8JgWKHj.js} +1 -1
- package/dist/assets/{any-language-editor-zUWBzcgZ.js → any-language-editor-D74trUk4.js} +1 -1
- package/dist/assets/{architectureDiagram-KFL7JDKH-BEl3GHb3.js → architectureDiagram-KFL7JDKH-DKH4sZc9.js} +1 -1
- package/dist/assets/{blockDiagram-ZYB65J3Q-DZZ4ZuBq.js → blockDiagram-ZYB65J3Q-CMUOsaA8.js} +1 -1
- package/dist/assets/{c4Diagram-AAMF2YG6-CpcSsgCn.js → c4Diagram-AAMF2YG6-ChBQRxTf.js} +1 -1
- package/dist/assets/channel-Nw6xcV0_.js +1 -0
- package/dist/assets/{chunk-ANTBXLJU-By7NXQdK.js → chunk-ANTBXLJU-DQX4DVWR.js} +1 -1
- package/dist/assets/{chunk-FHKO5MBM-Dbu5iw4P.js → chunk-FHKO5MBM-CX5q74rY.js} +1 -1
- package/dist/assets/{chunk-GLLZNHP4-eIqwWV8C.js → chunk-GLLZNHP4-DiE1hjiX.js} +1 -1
- package/dist/assets/{chunk-JBRWN2VN-pReAonKg.js → chunk-JBRWN2VN-ByCHd-l1.js} +1 -1
- package/dist/assets/{chunk-LXBSTHXV-BcgXnPW4.js → chunk-LXBSTHXV-wj6xGdxO.js} +1 -1
- package/dist/assets/{chunk-NRVI72HA-CeUG3PM2.js → chunk-NRVI72HA-NDJLJ21r.js} +1 -1
- package/dist/assets/{chunk-OMD6QJNC-BlyMhLkp.js → chunk-OMD6QJNC-CeqpBilh.js} +1 -1
- package/dist/assets/{chunk-WVR4S24B-DZ4tOHxI.js → chunk-WVR4S24B-Bhg3c-FI.js} +1 -1
- package/dist/assets/{circle-play-BzdTyGAJ.js → circle-play-CL_7z3E6.js} +1 -1
- package/dist/assets/classDiagram-3BZAVTQC-D7MCZkIX.js +1 -0
- package/dist/assets/classDiagram-v2-QTMF73CY-D7MCZkIX.js +1 -0
- package/dist/assets/clone--CScQp2h.js +1 -0
- package/dist/assets/{compile-C9PEBBgP.js → compile-EI1tParP.js} +6 -6
- package/dist/assets/{dagre-2BBEFEWP-8QI44xWy.js → dagre-2BBEFEWP-DXxGVBcA.js} +1 -1
- package/dist/assets/{data-grid-overlay-editor-DolR8QgL.js → data-grid-overlay-editor-Yb-vgT7v.js} +1 -1
- package/dist/assets/{diagram-4IRLE6MV-DNxf9_Tt.js → diagram-4IRLE6MV-GDYihhNl.js} +1 -1
- package/dist/assets/{diagram-GUPCWM2R-B5kCoaMW.js → diagram-GUPCWM2R-CHKYm2kT.js} +1 -1
- package/dist/assets/{diagram-RP2FKANI-BNrjp2H9.js → diagram-RP2FKANI-BiJtQ5s-.js} +1 -1
- package/dist/assets/{edit-page-CSZ43hY5.js → edit-page-adgLSKJm.js} +4 -4
- package/dist/assets/{erDiagram-HZWUO2LU-6NIZze7l.js → erDiagram-HZWUO2LU-CZQfIRvn.js} +1 -1
- package/dist/assets/{flowDiagram-THRYKUMA-DWnF-Gpv.js → flowDiagram-THRYKUMA-2EVwKW5L.js} +1 -1
- package/dist/assets/{ganttDiagram-WV7ZQ7D5-BBsLhZ14.js → ganttDiagram-WV7ZQ7D5-CeHc53Qh.js} +1 -1
- package/dist/assets/{gitGraphDiagram-OJR772UL-ZPug8QeF.js → gitGraphDiagram-OJR772UL-Di7zs1rZ.js} +1 -1
- package/dist/assets/{glide-data-editor-CwHen6fI.js → glide-data-editor-DVohJKLb.js} +11 -11
- package/dist/assets/{graph-ExmyslIm.js → graph-9M4U6_DC.js} +1 -1
- package/dist/assets/{home-page-D_hQ4zYA.js → home-page-DKHJaSX2.js} +1 -1
- package/dist/assets/{index-DckloZR-.js → index-B0rxx3jA.js} +1 -1
- package/dist/assets/{index-C0Wj5Jzr.js → index-B2abUDBC.js} +1 -1
- package/dist/assets/index-B6q_Qknx.css +1 -0
- package/dist/assets/{index-BAUNXdGL.js → index-B9dDxS0T.js} +1 -1
- package/dist/assets/{index-DS3C41QD.js → index-BEdPcSCM.js} +1 -1
- package/dist/assets/{index-BKUyx0mS.js → index-Bi-d0YuW.js} +1 -1
- package/dist/assets/{index-4tUCgJrK.js → index-Bsfo1EI2.js} +179 -179
- package/dist/assets/{index-BUxbtOCf.js → index-CXbOBQlF.js} +1 -1
- package/dist/assets/{index-BkVG8Ezw.js → index-CbX0t1zY.js} +1 -1
- package/dist/assets/{index-B8jhYY4Q.js → index-D6UF91Yd.js} +1 -1
- package/dist/assets/{index-CaB7U4FP.js → index-DAY5lnZg.js} +1 -1
- package/dist/assets/{index-CU0KdaDv.js → index-DHkVRTO2.js} +1 -1
- package/dist/assets/{index-GbAfFreJ.js → index-DOhcSEBM.js} +1 -1
- package/dist/assets/{index-CfEXxJ3T.js → index-DRmVgc5L.js} +1 -1
- package/dist/assets/{index-CH3lLikc.js → index-DVFcWBue.js} +1 -1
- package/dist/assets/{index-gOu4hCX5.js → index-DXB-4oSY.js} +1 -1
- package/dist/assets/{index-CQL-yanD.js → index-DnxjMywC.js} +1 -1
- package/dist/assets/{index-Dub9-uG5.js → index-DwVFqxHO.js} +1 -1
- package/dist/assets/{index-CirPC8om.js → index-DzboGbzP.js} +1 -1
- package/dist/assets/{index-BhFpILYz.js → index-OKP7vUaZ.js} +1 -1
- package/dist/assets/{index-BMsS2dpb.js → index-OXJkFv1q.js} +1 -1
- package/dist/assets/infoDiagram-6WOFNB3A-luodPqIi.js +2 -0
- package/dist/assets/{journeyDiagram-FFXJYRFH-Dn4HMJaD.js → journeyDiagram-FFXJYRFH-VrC0reP_.js} +1 -1
- package/dist/assets/{kanban-definition-KOZQBZVT-ZT4liW8Y.js → kanban-definition-KOZQBZVT-CGwvHACR.js} +1 -1
- package/dist/assets/{layout-DI5l3OPi.js → layout-DUb7CVE5.js} +1 -1
- package/dist/assets/{linear-CPQMGkWe.js → linear-BL0xpdEB.js} +1 -1
- package/dist/assets/links-MHOePrSD.js +17 -0
- package/dist/assets/{mermaid-CptFaguS.js → mermaid-DoAewE-x.js} +4 -4
- package/dist/assets/{min-vy6iZmQ8.js → min-DqPIBFeN.js} +1 -1
- package/dist/assets/{mindmap-definition-LNHGMQRG-BTCxqzhL.js → mindmap-definition-LNHGMQRG-B8KGMfpc.js} +1 -1
- package/dist/assets/{number-overlay-editor-BFOIhFAb.js → number-overlay-editor-CyE6WVNL.js} +1 -1
- package/dist/assets/{pieDiagram-DBDJKBY4-BbRB0h5c.js → pieDiagram-DBDJKBY4-Cdv4tpS2.js} +1 -1
- package/dist/assets/{quadrantDiagram-YPSRARAO-CMvMZqUo.js → quadrantDiagram-YPSRARAO-C5pqrfjc.js} +1 -1
- package/dist/assets/{react-plotly-CDxV4IQZ.js → react-plotly-Lyktqv5R.js} +1 -1
- package/dist/assets/{requirementDiagram-EGVEC5DT-BimhoPxR.js → requirementDiagram-EGVEC5DT-Bo_SBNZ7.js} +1 -1
- package/dist/assets/{run-page-BwNnXz3L.js → run-page-Bc2atWZU.js} +1 -1
- package/dist/assets/{sankeyDiagram-HRAUVNP4-CR-xEAVw.js → sankeyDiagram-HRAUVNP4-C7v3oPds.js} +1 -1
- package/dist/assets/{sequenceDiagram-WFGC7UMF-DFImDlJS.js → sequenceDiagram-WFGC7UMF-plW8zJPC.js} +1 -1
- package/dist/assets/{slides-component-CrzBd5wV.js → slides-component-D_jtIRQz.js} +1 -1
- package/dist/assets/{sortBy-HsdAxGp8.js → sortBy-DG2JAqpz.js} +1 -1
- package/dist/assets/{stateDiagram-UUKSUZ4H-C5QLrj8u.js → stateDiagram-UUKSUZ4H-F6nJx3uN.js} +1 -1
- package/dist/assets/stateDiagram-v2-EYPG3UTE-CJkd7kfZ.js +1 -0
- package/dist/assets/{storage-WAbj4_gY.js → storage-Cna8uiiu.js} +3 -3
- package/dist/assets/{terminal-DljZIC87.js → terminal-DKLt4Hkx.js} +1 -1
- package/dist/assets/{time-hUkQUPCR.js → time-7BLI5kFw.js} +1 -1
- package/dist/assets/{timeline-definition-3HZDQTIS-f65tu9_R.js → timeline-definition-3HZDQTIS-BZEc6IEw.js} +1 -1
- package/dist/assets/{tracing-DnVj1HQk.js → tracing-BKhwt1qJ.js} +2 -2
- package/dist/assets/{trash-I7VcGvbY.js → trash-gmRaV6SV.js} +1 -1
- package/dist/assets/{treemap-75Q7IDZK-fcJ8Xgt0.js → treemap-75Q7IDZK-D3nFwj9T.js} +1 -1
- package/dist/assets/{vega-component-cHs3295Y.js → vega-component-Du7NDJeA.js} +1 -1
- package/dist/assets/{xychartDiagram-FDP5SA34-BaT8AXVk.js → xychartDiagram-FDP5SA34-UO_Sd0Rd.js} +1 -1
- package/dist/index.html +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/dist/assets/_baseMap-COJxAmuQ.js +0 -1
- package/dist/assets/channel-xrKkbH4F.js +0 -1
- package/dist/assets/classDiagram-3BZAVTQC-Bkcbw80P.js +0 -1
- package/dist/assets/classDiagram-v2-QTMF73CY-Bkcbw80P.js +0 -1
- package/dist/assets/clone-Cr_v-fQp.js +0 -1
- package/dist/assets/index-QKsgBG6q.css +0 -1
- package/dist/assets/infoDiagram-6WOFNB3A-CxdJohuJ.js +0 -2
- package/dist/assets/links-BoHJb9MJ.js +0 -17
- package/dist/assets/stateDiagram-v2-EYPG3UTE-RVS1jN3C.js +0 -1
|
@@ -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();
|
|
@@ -78,21 +78,45 @@ describe("AiModelRegistry", () => {
|
|
|
78
78
|
const displayedModels = ["openai/gpt-4", "anthropic/claude-3-sonnet"];
|
|
79
79
|
const registry = AiModelRegistry.create({ displayedModels });
|
|
80
80
|
|
|
81
|
+
const ids = [...registry.getModelsMap().keys()];
|
|
82
|
+
expect(ids).toEqual(["openai/gpt-4", "anthropic/claude-3-sonnet"]);
|
|
81
83
|
expect(registry.getCustomModels()).toEqual(new Set());
|
|
82
84
|
expect(registry.getDisplayedModels()).toEqual(new Set(displayedModels));
|
|
83
85
|
});
|
|
84
86
|
|
|
85
87
|
it("should create registry with both custom and displayed models", () => {
|
|
86
88
|
const customModels = ["openai/custom-gpt"];
|
|
87
|
-
const displayedModels = ["openai/gpt
|
|
89
|
+
const displayedModels = ["openai/custom-gpt"];
|
|
88
90
|
const registry = AiModelRegistry.create({
|
|
89
91
|
customModels,
|
|
90
92
|
displayedModels,
|
|
91
93
|
});
|
|
92
94
|
|
|
95
|
+
const ids = [...registry.getModelsMap().keys()];
|
|
96
|
+
expect(ids).toEqual(["openai/custom-gpt"]);
|
|
93
97
|
expect(registry.getCustomModels()).toEqual(new Set(customModels));
|
|
94
98
|
expect(registry.getDisplayedModels()).toEqual(new Set(displayedModels));
|
|
95
99
|
});
|
|
100
|
+
|
|
101
|
+
it("should create registry with non-existent displayed_model", () => {
|
|
102
|
+
const customModels = ["openai/custom-gpt"];
|
|
103
|
+
const displayedModels = ["something-wrong/model-id"];
|
|
104
|
+
const registry = AiModelRegistry.create({
|
|
105
|
+
customModels,
|
|
106
|
+
displayedModels,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const ids = [...registry.getModelsMap().keys()];
|
|
110
|
+
// Include custom and all default ones.
|
|
111
|
+
expect(ids).toEqual([
|
|
112
|
+
"openai/custom-gpt",
|
|
113
|
+
"openai/gpt-4",
|
|
114
|
+
"anthropic/claude-3-sonnet",
|
|
115
|
+
"google/gemini-pro",
|
|
116
|
+
"openai/multi-model",
|
|
117
|
+
"anthropic/multi-model",
|
|
118
|
+
]);
|
|
119
|
+
});
|
|
96
120
|
});
|
|
97
121
|
|
|
98
122
|
describe("getModelsByProvider", () => {
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
} from "@marimo-team/llm-info";
|
|
8
8
|
import { models } from "@marimo-team/llm-info/models.json";
|
|
9
9
|
import { providers } from "@marimo-team/llm-info/providers.json";
|
|
10
|
+
import { Logger } from "@/utils/Logger";
|
|
10
11
|
import { MultiMap } from "@/utils/multi-map";
|
|
11
12
|
import { once } from "@/utils/once";
|
|
12
13
|
import type { ProviderId } from "./ids/ids";
|
|
@@ -14,6 +15,7 @@ import { AiModelId, type QualifiedModelId, type ShortModelId } from "./ids/ids";
|
|
|
14
15
|
|
|
15
16
|
export interface AiModel extends AiModelType {
|
|
16
17
|
roles: Role[];
|
|
18
|
+
model: ShortModelId;
|
|
17
19
|
providers: ProviderId[];
|
|
18
20
|
/** Whether this is a custom model. */
|
|
19
21
|
custom: boolean;
|
|
@@ -25,6 +27,7 @@ const getKnownModelMap = once((): ReadonlyMap<QualifiedModelId, AiModel> => {
|
|
|
25
27
|
const modelId = model.model as ShortModelId;
|
|
26
28
|
const modelInfo: AiModel = {
|
|
27
29
|
...model,
|
|
30
|
+
model: model.model as ShortModelId,
|
|
28
31
|
roles: model.roles.map((role) => role as Role),
|
|
29
32
|
providers: model.providers as ProviderId[],
|
|
30
33
|
custom: false,
|
|
@@ -84,14 +87,42 @@ export class AiModelRegistry {
|
|
|
84
87
|
* Builds the maps of models by provider and custom models.
|
|
85
88
|
*/
|
|
86
89
|
private buildMaps() {
|
|
87
|
-
|
|
90
|
+
let result = AiModelRegistry.buildMapsFromConfig({
|
|
91
|
+
displayedModels: this.displayedModels,
|
|
92
|
+
customModels: this.customModels,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// If we got zero results, then build the maps with no displayedModels
|
|
96
|
+
// This can happen if displayedModels is configured to non existent models
|
|
97
|
+
if (result.modelsMap.size === 0) {
|
|
98
|
+
Logger.error(
|
|
99
|
+
"The configured displayed_models have filtered out all registered models. Reverting back to showing all models.",
|
|
100
|
+
[...this.displayedModels],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
result = AiModelRegistry.buildMapsFromConfig({
|
|
104
|
+
displayedModels: new Set(),
|
|
105
|
+
customModels: this.customModels,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.modelsByProviderMap = result.modelsByProviderMap;
|
|
110
|
+
this.modelsMap = result.modelsMap;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private static buildMapsFromConfig(opts: {
|
|
114
|
+
customModels: ReadonlySet<QualifiedModelId>;
|
|
115
|
+
displayedModels: ReadonlySet<QualifiedModelId>;
|
|
116
|
+
}) {
|
|
117
|
+
const { displayedModels, customModels } = opts;
|
|
88
118
|
const hasDisplayedModels = displayedModels.size > 0;
|
|
89
119
|
const knownModelMap = getKnownModelMap();
|
|
90
120
|
const customModelsMap = new Map<QualifiedModelId, AiModel>();
|
|
91
121
|
|
|
92
122
|
let modelsMap = new Map<QualifiedModelId, AiModel>();
|
|
123
|
+
const modelsByProviderMap = new MultiMap<ProviderId, AiModel>();
|
|
93
124
|
|
|
94
|
-
for (const model of
|
|
125
|
+
for (const model of customModels) {
|
|
95
126
|
if (hasDisplayedModels && !displayedModels.has(model)) {
|
|
96
127
|
continue;
|
|
97
128
|
}
|
|
@@ -121,15 +152,16 @@ export class AiModelRegistry {
|
|
|
121
152
|
}
|
|
122
153
|
|
|
123
154
|
// Set custom models first, then known models
|
|
155
|
+
// Known models will overwrite custom models (which is desired)
|
|
124
156
|
modelsMap = new Map([...customModelsMap, ...modelsMap]);
|
|
125
157
|
|
|
126
158
|
// Group by provider
|
|
127
159
|
for (const [qualifiedModelId, model] of modelsMap.entries()) {
|
|
128
160
|
const modelId = AiModelId.parse(qualifiedModelId);
|
|
129
|
-
|
|
161
|
+
modelsByProviderMap.add(modelId.providerId, model);
|
|
130
162
|
}
|
|
131
163
|
|
|
132
|
-
|
|
164
|
+
return { modelsByProviderMap, modelsMap };
|
|
133
165
|
}
|
|
134
166
|
|
|
135
167
|
getDisplayedModels() {
|
package/src/hooks/useDebounce.ts
CHANGED
|
@@ -84,7 +84,8 @@ export function useDebounceControlledState<T>(opts: {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
88
|
+
export function useDebouncedCallback<T extends (...args: any[]) => unknown>(
|
|
88
89
|
callback: T,
|
|
89
90
|
delay: number,
|
|
90
91
|
) {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{b as m}from"./_baseEach-rPPiuvyD.js";import{x as s}from"./index-4tUCgJrK.js";function e(r,o){var a=-1,t=s(r)?Array(r.length):[];return m(r,function(n,f,i){t[++a]=o(n,f,i)}),t}export{e as b};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{U as s,C as o}from"./mermaid-CptFaguS.js";const n=(a,r)=>s.lang.round(o.parse(a)[r]);export{n as c};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as t,C as o}from"./chunk-JBRWN2VN-pReAonKg.js";import{_ as e}from"./mermaid-CptFaguS.js";import"./transform-B8bpuzxV.js";import"./chunk-GLLZNHP4-eIqwWV8C.js";import"./chunk-WVR4S24B-DZ4tOHxI.js";import"./chunk-NRVI72HA-CeUG3PM2.js";import"./index-4tUCgJrK.js";import"./step-BwsUM5iJ.js";import"./timer-BwIYMJWC.js";var i={parser:t,get db(){return new o},renderer:s,styles:a,init:e(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{i as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{s as a,c as s,a as t,C as o}from"./chunk-JBRWN2VN-pReAonKg.js";import{_ as e}from"./mermaid-CptFaguS.js";import"./transform-B8bpuzxV.js";import"./chunk-GLLZNHP4-eIqwWV8C.js";import"./chunk-WVR4S24B-DZ4tOHxI.js";import"./chunk-NRVI72HA-CeUG3PM2.js";import"./index-4tUCgJrK.js";import"./step-BwsUM5iJ.js";import"./timer-BwIYMJWC.js";var i={parser:t,get db(){return new o},renderer:s,styles:a,init:e(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{i as diagram};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{b as n}from"./_baseUniq-PFDhmjrU.js";function o(r){return n(r,4)}export{o as c};
|