@marimo-team/frontend 0.19.3-dev8 → 0.19.4-dev0
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/{CellStatus-BwPGnX3z.js → CellStatus--kUu6N2K.js} +1 -1
- package/dist/assets/{ConnectedDataExplorerComponent-KlUs_Sz3.js → ConnectedDataExplorerComponent-BKJwCHu7.js} +1 -1
- package/dist/assets/{ErrorBoundary-Drf1manw.js → ErrorBoundary-C7JBxSzd.js} +1 -1
- package/dist/assets/{ImperativeModal-q6QlC2aZ.js → ImperativeModal-DVhvP4lH.js} +1 -1
- package/dist/assets/{JsonOutput--AuyEErr.js → JsonOutput-BSGE-MRo.js} +5 -5
- package/dist/assets/{LazyAnyLanguageCodeMirror-jpEDlD0M.js → LazyAnyLanguageCodeMirror-Cp2punaU.js} +2 -2
- package/dist/assets/{MarimoErrorOutput-BZjY8e2w.js → MarimoErrorOutput-CX0SCJOZ.js} +2 -2
- package/dist/assets/{RenderHTML-BTLaM20B.js → RenderHTML-Do_PVqRy.js} +1 -1
- package/dist/assets/VisuallyHidden-B9t3FhTP.js +1 -0
- package/dist/assets/{add-cell-with-ai-BWWVs9qV.js → add-cell-with-ai-manh7kBT.js} +21 -21
- package/dist/assets/{add-database-form-Bw_YRH1r.js → add-database-form-CgkV0MRs.js} +2 -2
- package/dist/assets/agent-panel-D-OmT-rw.js +287 -0
- package/dist/assets/{ai-model-dropdown-BrUOgnWS.js → ai-model-dropdown-DzyBY5VA.js} +1 -1
- package/dist/assets/{alert-dialog-k5KxevGr.js → alert-dialog-jcHA5geR.js} +1 -1
- package/dist/assets/{any-language-editor-DQu1Tt2N.js → any-language-editor-Cm83E7D_.js} +1 -1
- package/dist/assets/{app-config-button-B8CXELx0.js → app-config-button-DC3alCuB.js} +1 -1
- package/dist/assets/button-B8cGZzP5.js +1 -0
- package/dist/assets/{cache-panel-C1So4Zu3.js → cache-panel-1FqnpB9y.js} +1 -1
- package/dist/assets/cell-editor-RHFZmO74.js +23 -0
- package/dist/assets/cell-link-Dqj_nfXA.js +1 -0
- package/dist/assets/{cells-DU3EySUd.js → cells-BNQUQiDS.js} +49 -49
- package/dist/assets/{chat-components-Bc9j9ls4.js → chat-components-CWiXtKu6.js} +1 -1
- package/dist/assets/{chat-display-BrTi6c8V.js → chat-display-CGnOamQG.js} +1 -1
- package/dist/assets/{chat-panel-8Dym5Gv3.js → chat-panel-Dh1M55c9.js} +2 -2
- package/dist/assets/client-CDjmJmVw.js +4 -0
- package/dist/assets/{column-preview-Ck6B_-sQ.js → column-preview-CKxT2s-S.js} +1 -1
- package/dist/assets/{command-B_minI8b.js → command-YPFTinLj.js} +1 -1
- package/dist/assets/{command-palette-BT3u6JBB.js → command-palette-7fVEhKGc.js} +1 -1
- package/dist/assets/common-DJkPpBxC.js +1 -0
- package/dist/assets/config-D6nhy4FA.js +1 -0
- package/dist/assets/context-DHfVoQfl.js +1 -0
- package/dist/assets/{copy-icon-B69c-352.js → copy-icon-jWsqdLn1.js} +1 -1
- package/dist/assets/{datasource-DCvPlnaJ.js → datasource-DerBLc6V.js} +2 -2
- package/dist/assets/{dependency-graph-panel-C9jYZ6pA.js → dependency-graph-panel-Vd-OsVLa.js} +4 -4
- package/dist/assets/{dialog-DUEuLcT2.js → dialog-CF5DtF1E.js} +1 -1
- package/dist/assets/{dist-DOFFh6Ii.js → dist-Dg7UO_Vw.js} +1 -1
- package/dist/assets/{documentation-panel-AsatrTfg.js → documentation-panel-xG2-zpwg.js} +1 -1
- package/dist/assets/{download-PR1bF3g_.js → download-B6EJS7Ar.js} +1 -1
- package/dist/assets/edit-page-7Hkti2j_.js +12 -0
- package/dist/assets/{error-banner-DU5Qb8a8.js → error-banner-DvT0IGDZ.js} +1 -1
- package/dist/assets/{error-panel-D_wVKV6I.js → error-panel-BxBpZYvt.js} +1 -1
- package/dist/assets/{es-CEE_7T0w.js → es-BoHEdemq.js} +1 -1
- package/dist/assets/{field-DDKGFzpC.js → field-Clr_fqUr.js} +1 -1
- package/dist/assets/{file-explorer-panel-DltK8JVp.js → file-explorer-panel-C9K0vIPl.js} +1 -1
- package/dist/assets/{floating-outline-BfdazXWm.js → floating-outline-DCrTuu2G.js} +1 -1
- package/dist/assets/{focus-CtlWIiQr.js → focus-DM53w5BH.js} +1 -1
- package/dist/assets/{form-Cy5TkLzh.js → form-BcKfhfZc.js} +2 -2
- package/dist/assets/{glide-data-editor-D_bRnWfy.js → glide-data-editor-CRb9AiCG.js} +1 -1
- package/dist/assets/{globals-BSLm1nlz.js → globals-Bf30kOQF.js} +1 -1
- package/dist/assets/{home-page-CEnaUutq.js → home-page-BRyNf7fl.js} +2 -2
- package/dist/assets/index-CBMqMxiq.js +43 -0
- package/dist/assets/index-DDc_1b-N.css +2 -0
- package/dist/assets/input-B80Yt1uu.js +1 -0
- package/dist/assets/{kiosk-mode-BnTZR6mM.js → kiosk-mode-P-NYHJID.js} +1 -1
- package/dist/assets/{label-qwandMoh.js → label-CNZLffHW.js} +1 -1
- package/dist/assets/{layout-BTiWDrbh.js → layout-DT91GUei.js} +4 -4
- package/dist/assets/links-D529u6GQ.js +1 -0
- package/dist/assets/{logs-panel-Cnp9tO_1.js → logs-panel-C2dfrRig.js} +1 -1
- package/dist/assets/{markdown-renderer-BSrbBHwX.js → markdown-renderer-BPnVa0ym.js} +2 -2
- package/dist/assets/{mermaid-BPkO79lo.js → mermaid--ZwxKP7u.js} +1 -1
- package/dist/assets/mode-Dq8MKjNR.js +1 -0
- package/dist/assets/{multi-map-fjX9ImVF.js → multi-map-CQd4MZr5.js} +1 -1
- package/dist/assets/name-cell-input-BaEPC7ON.js +1 -0
- package/dist/assets/{outline-panel-DCfj1bI-.js → outline-panel-Cca864H0.js} +1 -1
- package/dist/assets/{packages-panel-BiEckVdM.js → packages-panel-Cy_KAYmq.js} +1 -1
- package/dist/assets/{panels-BXRys72u.js → panels-BzlLZfye.js} +1 -1
- package/dist/assets/{process-output-wGlHkL-Q.js → process-output-Dn1rOp26.js} +1 -1
- package/dist/assets/{readonly-python-code-xbh7G2Y2.js → readonly-python-code-CXeF74Iq.js} +1 -1
- package/dist/assets/{renderShortcut-D0Pei-OA.js → renderShortcut-eU5Hsfml.js} +1 -1
- package/dist/assets/{run-page-BCwJRhCq.js → run-page-CM_n6pXD.js} +1 -1
- package/dist/assets/scratchpad-panel-XCkVY3Hp.js +1 -0
- package/dist/assets/{secrets-panel-CDWmmmBS.js → secrets-panel-BMY6PPth.js} +1 -1
- package/dist/assets/{select-D0g5GnIs.js → select-D9lTzMzP.js} +1 -1
- package/dist/assets/{session-panel-DuQl_oQp.js → session-panel-BDt6Y_mU.js} +1 -1
- package/dist/assets/{slides-component-MkPkpql1.js → slides-component-Dp0Yv5b0.js} +1 -1
- package/dist/assets/{snippets-panel-R_ql6HGu.js → snippets-panel-K-JKJQBf.js} +1 -1
- package/dist/assets/state-DWRZTH2y.js +1 -0
- package/dist/assets/state-JzO-Ni5T.js +1 -0
- package/dist/assets/{switch-CWzL-0WF.js → switch-RowEjq0T.js} +1 -1
- package/dist/assets/{terminal-BWM0fOMh.js → terminal-BhbNfCNw.js} +1 -1
- package/dist/assets/{textarea-CfvBt_Xm.js → textarea-Di1KKcL4.js} +1 -1
- package/dist/assets/{tracing-Kscqc1t3.js → tracing-nvbrZdpf.js} +1 -1
- package/dist/assets/{tracing-panel-BEzOflWc.js → tracing-panel-CTXJaO-A.js} +2 -2
- package/dist/assets/{types-DhuSHMNQ.js → types-CT2U5Ljy.js} +1 -1
- package/dist/assets/{useAddCell-C9lbOVO1.js → useAddCell-COb93CUl.js} +1 -1
- package/dist/assets/{useBoolean-B-A0dyIW.js → useBoolean-B_S7yTZz.js} +1 -1
- package/dist/assets/{useCellActionButton-fsh9MTAX.js → useCellActionButton-D5Zt1dDz.js} +1 -1
- package/dist/assets/{useDateFormatter-CV0QXb5P.js → useDateFormatter-DsANziQR.js} +1 -1
- package/dist/assets/useDeleteCell-DHF_xvAh.js +1 -0
- package/dist/assets/{useDependencyPanelTab-CngFbla0.js → useDependencyPanelTab-D59iW_MD.js} +1 -1
- package/dist/assets/useInterval-BGPIviJp.js +1 -0
- package/dist/assets/{useNotebookActions-D01w160c.js → useNotebookActions-DEl-rH-3.js} +1 -1
- package/dist/assets/{useNumberFormatter-D8ks3oPN.js → useNumberFormatter-FoXhpyAb.js} +1 -1
- package/dist/assets/usePress-DTwIUo40.js +7 -0
- package/dist/assets/useRunCells-CKEmgeKM.js +1 -0
- package/dist/assets/useSplitCell-D9YiO-z5.js +1 -0
- package/dist/assets/{useTheme-DfP1CWaW.js → useTheme-CNj0G_ol.js} +1 -1
- package/dist/assets/utilities.esm-DG4qccZc.js +3 -0
- package/dist/assets/utils-pfqq9IdB.js +1 -0
- package/dist/assets/{vega-component-B8ghmMYW.js → vega-component-C1voDf5W.js} +1 -1
- package/dist/assets/{write-secret-modal-CLm48gMe.js → write-secret-modal-hOetwavI.js} +1 -1
- package/dist/index.html +56 -56
- package/package.json +5 -5
- package/src/__mocks__/requests.ts +1 -0
- package/src/__tests__/mount.test.ts +128 -0
- package/src/components/app-config/__tests__/get-dirty-values.test.ts +1 -1
- package/src/components/app-config/ai-config.tsx +328 -28
- package/src/components/app-config/user-config-form.tsx +10 -3
- package/src/components/chat/acp/agent-panel.tsx +56 -43
- package/src/components/chat/chat-utils.ts +0 -19
- package/src/components/data-table/column-header.tsx +1 -1
- package/src/components/editor/KernelStartupErrorModal.tsx +2 -2
- package/src/components/editor/actions/name-cell-input.tsx +10 -4
- package/src/components/editor/ai/completion-handlers.tsx +1 -1
- package/src/components/editor/alerts/connecting-alert.tsx +33 -6
- package/src/components/editor/chrome/types.ts +2 -4
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +55 -58
- package/src/components/editor/chrome/wrapper/footer-items/runtime-settings.tsx +150 -96
- package/src/components/editor/renderers/vertical-layout/__tests__/useFocusFirstEditor.test.ts +27 -0
- package/src/components/editor/renderers/vertical-layout/useFocusFirstEditor.ts +6 -0
- package/src/components/utils/lazy-mount.tsx +29 -8
- package/src/core/ai/ids/ids.ts +12 -4
- package/src/core/cells/cells.ts +2 -0
- package/src/core/cells/scrollCellIntoView.ts +3 -2
- package/src/core/codemirror/cm.ts +2 -0
- package/src/core/codemirror/lsp/__tests__/notebook-lsp.test.ts +123 -0
- package/src/core/codemirror/lsp/notebook-lsp.ts +44 -4
- package/src/core/codemirror/misc/__tests__/string-braces.test.ts +200 -0
- package/src/core/codemirror/misc/string-braces.ts +37 -0
- package/src/core/config/__tests__/config-schema.test.ts +36 -0
- package/src/core/config/config-schema.ts +1 -0
- package/src/core/export/__tests__/hooks.test.ts +504 -0
- package/src/core/export/hooks.ts +93 -4
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/kernel/__tests__/handlers.test.ts +2 -2
- package/src/core/kernel/state.ts +1 -0
- package/src/core/network/__tests__/requests-lazy.test.ts +1 -1
- package/src/core/network/__tests__/requests-network.test.ts +0 -18
- package/src/core/network/requests-lazy.ts +3 -2
- package/src/core/network/requests-network.ts +10 -7
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.tsx +1 -0
- package/src/core/network/types.ts +2 -0
- package/src/core/wasm/bridge.ts +1 -0
- package/src/css/globals.css +2 -0
- package/src/hooks/__tests__/useInterval.test.tsx +104 -0
- package/src/hooks/useInterval.ts +32 -6
- package/src/mount.tsx +6 -0
- package/src/plugins/impl/chat/ChatPlugin.tsx +2 -4
- package/src/plugins/impl/chat/chat-ui.tsx +62 -191
- package/src/plugins/impl/chat/types.ts +5 -12
- package/src/plugins/impl/data-frames/DataFramePlugin.tsx +3 -1
- package/src/utils/events.ts +1 -0
- package/dist/assets/VisuallyHidden-BodIky8L.js +0 -1
- package/dist/assets/agent-panel-Bm-vW8YL.js +0 -287
- package/dist/assets/button-DuYGqRtX.js +0 -1
- package/dist/assets/cell-editor-Cdtc1m3g.js +0 -23
- package/dist/assets/cell-link-CFAPzUg5.js +0 -1
- package/dist/assets/client-DfkWorYM.js +0 -4
- package/dist/assets/common-jorbwXZC.js +0 -1
- package/dist/assets/config-Ba3eeYri.js +0 -1
- package/dist/assets/context-BAYdLMF_.js +0 -1
- package/dist/assets/edit-page-B1Ed6RKp.js +0 -12
- package/dist/assets/index-DNg7_e7t.js +0 -43
- package/dist/assets/index-__6MNWbe.css +0 -2
- package/dist/assets/input-CaEtLL8p.js +0 -1
- package/dist/assets/links-Bpd4gqTj.js +0 -1
- package/dist/assets/mode-yhfN-4ye.js +0 -1
- package/dist/assets/name-cell-input-CmuWqgFR.js +0 -1
- package/dist/assets/scratchpad-panel-C6PpCYtK.js +0 -1
- package/dist/assets/state-DEHWsmkM.js +0 -1
- package/dist/assets/state-DXAf-ejz.js +0 -1
- package/dist/assets/useDeleteCell-ByImoTpm.js +0 -1
- package/dist/assets/useInterval-DpipYmgs.js +0 -1
- package/dist/assets/usePress-C2LPFxyv.js +0 -7
- package/dist/assets/useRunCells-CmnSPQtM.js +0 -1
- package/dist/assets/useSplitCell-BTH64tve.js +0 -1
- package/dist/assets/utilities.esm-CMQs6YPp.js +0 -3
- package/dist/assets/utils-CJJIceVn.js +0 -1
|
@@ -34,7 +34,8 @@ import { Textarea } from "@/components/ui/textarea";
|
|
|
34
34
|
import type { SupportedRole } from "@/core/ai/config";
|
|
35
35
|
import {
|
|
36
36
|
AiModelId,
|
|
37
|
-
|
|
37
|
+
KNOWN_PROVIDERS,
|
|
38
|
+
type KnownProviderId,
|
|
38
39
|
type ProviderId,
|
|
39
40
|
type QualifiedModelId,
|
|
40
41
|
type ShortModelId,
|
|
@@ -90,6 +91,11 @@ interface AiProviderTitleProps {
|
|
|
90
91
|
children: React.ReactNode;
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
interface CustomProviderConfig {
|
|
95
|
+
api_key?: string;
|
|
96
|
+
base_url?: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
93
99
|
export const AiProviderTitle: React.FC<AiProviderTitleProps> = ({
|
|
94
100
|
provider,
|
|
95
101
|
children,
|
|
@@ -555,15 +561,18 @@ const AccordionFormItem = ({
|
|
|
555
561
|
provider,
|
|
556
562
|
children,
|
|
557
563
|
isConfigured,
|
|
564
|
+
value,
|
|
558
565
|
}: {
|
|
559
566
|
title: string;
|
|
560
567
|
triggerClassName?: string;
|
|
561
568
|
provider: AiProviderIconProps["provider"];
|
|
562
569
|
children: React.ReactNode;
|
|
563
570
|
isConfigured: boolean;
|
|
571
|
+
/** Custom value for the accordion item. Defaults to provider. */
|
|
572
|
+
value?: string;
|
|
564
573
|
}) => {
|
|
565
574
|
return (
|
|
566
|
-
<AccordionItem value={provider}>
|
|
575
|
+
<AccordionItem value={value ?? provider}>
|
|
567
576
|
<AccordionTrigger className={triggerClassName}>
|
|
568
577
|
<AiProviderTitle provider={provider}>
|
|
569
578
|
{title}
|
|
@@ -581,9 +590,228 @@ const AccordionFormItem = ({
|
|
|
581
590
|
);
|
|
582
591
|
};
|
|
583
592
|
|
|
593
|
+
export const CustomProvidersConfig: React.FC<AiConfigProps> = ({
|
|
594
|
+
form,
|
|
595
|
+
config,
|
|
596
|
+
onSubmit,
|
|
597
|
+
}) => {
|
|
598
|
+
const [isAddingProvider, setIsAddingProvider] = useState(false);
|
|
599
|
+
const [newProviderName, setNewProviderName] = useState("");
|
|
600
|
+
const [newProviderApiKey, setNewProviderApiKey] = useState("");
|
|
601
|
+
const [newProviderBaseUrl, setNewProviderBaseUrl] = useState("");
|
|
602
|
+
|
|
603
|
+
const providerNameInputId = useId();
|
|
604
|
+
const apiKeyInputId = useId();
|
|
605
|
+
const baseUrlInputId = useId();
|
|
606
|
+
|
|
607
|
+
const normalizedName = newProviderName.toLowerCase().replaceAll(/\s+/g, "_");
|
|
608
|
+
const customProviders = form.watch("ai.custom_providers");
|
|
609
|
+
const isDuplicate =
|
|
610
|
+
KNOWN_PROVIDERS.includes(normalizedName as KnownProviderId) ||
|
|
611
|
+
(customProviders && Object.keys(customProviders).includes(normalizedName));
|
|
612
|
+
|
|
613
|
+
const hasValidValues =
|
|
614
|
+
normalizedName.trim() && newProviderBaseUrl.trim() && !isDuplicate;
|
|
615
|
+
|
|
616
|
+
const resetForm = () => {
|
|
617
|
+
setNewProviderName("");
|
|
618
|
+
setNewProviderApiKey("");
|
|
619
|
+
setNewProviderBaseUrl("");
|
|
620
|
+
setIsAddingProvider(false);
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
return (
|
|
624
|
+
<FormField
|
|
625
|
+
control={form.control}
|
|
626
|
+
name="ai.custom_providers"
|
|
627
|
+
render={({ field }) => {
|
|
628
|
+
const customProviders = (field.value || {}) as Record<
|
|
629
|
+
string,
|
|
630
|
+
CustomProviderConfig
|
|
631
|
+
>;
|
|
632
|
+
const customProviderEntries = Object.entries(customProviders);
|
|
633
|
+
|
|
634
|
+
const addProvider = () => {
|
|
635
|
+
if (!hasValidValues) {
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
field.onChange({
|
|
639
|
+
...customProviders,
|
|
640
|
+
[normalizedName]: {
|
|
641
|
+
api_key: newProviderApiKey || undefined,
|
|
642
|
+
base_url: newProviderBaseUrl,
|
|
643
|
+
},
|
|
644
|
+
});
|
|
645
|
+
onSubmit(form.getValues());
|
|
646
|
+
resetForm();
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
const removeProvider = (providerName: string) => {
|
|
650
|
+
const { [providerName]: _, ...rest } = customProviders;
|
|
651
|
+
// Reset to clear nested dirty state, then set new value
|
|
652
|
+
form.resetField("ai.custom_providers");
|
|
653
|
+
form.setValue("ai.custom_providers", rest, { shouldDirty: true });
|
|
654
|
+
onSubmit(form.getValues());
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
const providerForm = (
|
|
658
|
+
<div className="flex flex-col gap-3 p-4 border border-border rounded-md bg-muted/20">
|
|
659
|
+
<div className="flex flex-col gap-1.5">
|
|
660
|
+
<Label htmlFor={providerNameInputId}>Provider Name</Label>
|
|
661
|
+
<Input
|
|
662
|
+
id={providerNameInputId}
|
|
663
|
+
placeholder="e.g., together, groq, mistral"
|
|
664
|
+
value={newProviderName}
|
|
665
|
+
onChange={(e) => setNewProviderName(e.target.value)}
|
|
666
|
+
/>
|
|
667
|
+
{isDuplicate && (
|
|
668
|
+
<p className="text-xs text-destructive">
|
|
669
|
+
A provider with this name already exists.
|
|
670
|
+
</p>
|
|
671
|
+
)}
|
|
672
|
+
{newProviderName && (
|
|
673
|
+
<p className="text-xs text-muted-secondary">
|
|
674
|
+
Use models with prefix:{" "}
|
|
675
|
+
<Kbd className="inline text-xs">{normalizedName}/</Kbd>
|
|
676
|
+
</p>
|
|
677
|
+
)}
|
|
678
|
+
</div>
|
|
679
|
+
|
|
680
|
+
<div className="flex flex-col gap-1.5">
|
|
681
|
+
<Label htmlFor={baseUrlInputId}>
|
|
682
|
+
Base URL <span className="text-destructive">*</span>
|
|
683
|
+
</Label>
|
|
684
|
+
<Input
|
|
685
|
+
id={baseUrlInputId}
|
|
686
|
+
placeholder="e.g., https://api.together.xyz/v1"
|
|
687
|
+
value={newProviderBaseUrl}
|
|
688
|
+
onChange={(e) => setNewProviderBaseUrl(e.target.value)}
|
|
689
|
+
/>
|
|
690
|
+
</div>
|
|
691
|
+
|
|
692
|
+
<div className="flex flex-col gap-1.5">
|
|
693
|
+
<Label htmlFor={apiKeyInputId}>API Key (optional)</Label>
|
|
694
|
+
<Input
|
|
695
|
+
id={apiKeyInputId}
|
|
696
|
+
placeholder="sk-..."
|
|
697
|
+
type="password"
|
|
698
|
+
value={newProviderApiKey}
|
|
699
|
+
onChange={(e) => setNewProviderApiKey(e.target.value)}
|
|
700
|
+
/>
|
|
701
|
+
</div>
|
|
702
|
+
|
|
703
|
+
<div className="flex gap-2 mt-1">
|
|
704
|
+
<Button
|
|
705
|
+
onClick={addProvider}
|
|
706
|
+
disabled={!hasValidValues}
|
|
707
|
+
size="xs"
|
|
708
|
+
>
|
|
709
|
+
Add Provider
|
|
710
|
+
</Button>
|
|
711
|
+
<Button variant="outline" onClick={resetForm} size="xs">
|
|
712
|
+
Cancel
|
|
713
|
+
</Button>
|
|
714
|
+
</div>
|
|
715
|
+
</div>
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
const renderAccordionItem = ({
|
|
719
|
+
providerName,
|
|
720
|
+
providerConfig,
|
|
721
|
+
onRemove,
|
|
722
|
+
}: {
|
|
723
|
+
providerName: string;
|
|
724
|
+
providerConfig: CustomProviderConfig;
|
|
725
|
+
onRemove: (name: string) => void;
|
|
726
|
+
}) => {
|
|
727
|
+
const displayName = Strings.startCase(providerName);
|
|
728
|
+
const isConfigured =
|
|
729
|
+
!!providerConfig.api_key || !!providerConfig.base_url;
|
|
730
|
+
|
|
731
|
+
return (
|
|
732
|
+
<AccordionFormItem
|
|
733
|
+
key={`custom-${providerName}`}
|
|
734
|
+
title={displayName}
|
|
735
|
+
provider={providerName}
|
|
736
|
+
value={`custom-${providerName}`}
|
|
737
|
+
isConfigured={isConfigured}
|
|
738
|
+
>
|
|
739
|
+
<ApiKey
|
|
740
|
+
form={form}
|
|
741
|
+
config={config}
|
|
742
|
+
name={
|
|
743
|
+
`ai.custom_providers.${providerName}.api_key` as FieldPath<UserConfig>
|
|
744
|
+
}
|
|
745
|
+
placeholder="sk-..."
|
|
746
|
+
testId={`custom-provider-${providerName}-api-key`}
|
|
747
|
+
/>
|
|
748
|
+
<BaseUrl
|
|
749
|
+
form={form}
|
|
750
|
+
config={config}
|
|
751
|
+
name={
|
|
752
|
+
`ai.custom_providers.${providerName}.base_url` as FieldPath<UserConfig>
|
|
753
|
+
}
|
|
754
|
+
placeholder="https://api.example.com/v1"
|
|
755
|
+
testId={`custom-provider-${providerName}-base-url`}
|
|
756
|
+
/>
|
|
757
|
+
<Button
|
|
758
|
+
variant="destructive"
|
|
759
|
+
size="xs"
|
|
760
|
+
onClick={(e) => {
|
|
761
|
+
e.stopPropagation();
|
|
762
|
+
e.preventDefault();
|
|
763
|
+
onRemove(providerName);
|
|
764
|
+
}}
|
|
765
|
+
className="w-fit self-end"
|
|
766
|
+
>
|
|
767
|
+
<Trash2Icon className="h-4 w-4 mr-2" />
|
|
768
|
+
Remove Provider
|
|
769
|
+
</Button>
|
|
770
|
+
</AccordionFormItem>
|
|
771
|
+
);
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
return (
|
|
775
|
+
<SettingGroup>
|
|
776
|
+
<SettingSubtitle>Custom Providers</SettingSubtitle>
|
|
777
|
+
<p className="text-sm text-muted-secondary">
|
|
778
|
+
Add your own OpenAI-compatible provider. Once added, you can
|
|
779
|
+
configure models in the AI Models tab.
|
|
780
|
+
</p>
|
|
781
|
+
|
|
782
|
+
{customProviderEntries.length > 0 && (
|
|
783
|
+
<Accordion type="multiple" className="-mt-4">
|
|
784
|
+
{customProviderEntries.map(([name, providerConfig]) =>
|
|
785
|
+
renderAccordionItem({
|
|
786
|
+
providerName: name,
|
|
787
|
+
providerConfig,
|
|
788
|
+
onRemove: removeProvider,
|
|
789
|
+
}),
|
|
790
|
+
)}
|
|
791
|
+
</Accordion>
|
|
792
|
+
)}
|
|
793
|
+
|
|
794
|
+
{isAddingProvider ? (
|
|
795
|
+
providerForm
|
|
796
|
+
) : (
|
|
797
|
+
<AddButton
|
|
798
|
+
className="self-start"
|
|
799
|
+
isFormOpen={isAddingProvider}
|
|
800
|
+
setIsFormOpen={setIsAddingProvider}
|
|
801
|
+
label="Add Provider"
|
|
802
|
+
/>
|
|
803
|
+
)}
|
|
804
|
+
</SettingGroup>
|
|
805
|
+
);
|
|
806
|
+
}}
|
|
807
|
+
/>
|
|
808
|
+
);
|
|
809
|
+
};
|
|
810
|
+
|
|
584
811
|
export const AiProvidersConfig: React.FC<AiConfigProps> = ({
|
|
585
812
|
form,
|
|
586
813
|
config,
|
|
814
|
+
onSubmit,
|
|
587
815
|
}) => {
|
|
588
816
|
const isWasmRuntime = isWasm();
|
|
589
817
|
|
|
@@ -903,13 +1131,17 @@ export const AiProvidersConfig: React.FC<AiConfigProps> = ({
|
|
|
903
1131
|
</AccordionFormItem>
|
|
904
1132
|
|
|
905
1133
|
<AccordionFormItem
|
|
906
|
-
title="OpenAI-Compatible"
|
|
1134
|
+
title="OpenAI-Compatible (Legacy)"
|
|
907
1135
|
provider="openai-compatible"
|
|
908
1136
|
isConfigured={
|
|
909
1137
|
hasValue("ai.open_ai_compatible.api_key") &&
|
|
910
1138
|
hasValue("ai.open_ai_compatible.base_url")
|
|
911
1139
|
}
|
|
912
1140
|
>
|
|
1141
|
+
<p className="text-sm text-amber-600 dark:text-amber-400 mb-2">
|
|
1142
|
+
Consider using Custom Providers instead, which allows you to add
|
|
1143
|
+
multiple providers with distinct names.
|
|
1144
|
+
</p>
|
|
913
1145
|
<ApiKey
|
|
914
1146
|
form={form}
|
|
915
1147
|
config={config}
|
|
@@ -933,6 +1165,8 @@ export const AiProvidersConfig: React.FC<AiConfigProps> = ({
|
|
|
933
1165
|
/>
|
|
934
1166
|
</AccordionFormItem>
|
|
935
1167
|
</Accordion>
|
|
1168
|
+
|
|
1169
|
+
<CustomProvidersConfig form={form} config={config} onSubmit={onSubmit} />
|
|
936
1170
|
</SettingGroup>
|
|
937
1171
|
);
|
|
938
1172
|
};
|
|
@@ -1133,6 +1367,16 @@ export const AiModelDisplayConfig: React.FC<AiConfigProps> = ({
|
|
|
1133
1367
|
name: "ai.models.custom_models",
|
|
1134
1368
|
}) as QualifiedModelId[];
|
|
1135
1369
|
|
|
1370
|
+
const customProviders = useWatch({
|
|
1371
|
+
control: form.control,
|
|
1372
|
+
name: "ai.custom_providers",
|
|
1373
|
+
}) as Record<string, CustomProviderConfig> | undefined;
|
|
1374
|
+
|
|
1375
|
+
const customProviderNames = useMemo(
|
|
1376
|
+
() => Object.keys(customProviders || {}),
|
|
1377
|
+
[customProviders],
|
|
1378
|
+
);
|
|
1379
|
+
|
|
1136
1380
|
const aiModelRegistry = useMemo(
|
|
1137
1381
|
() =>
|
|
1138
1382
|
AiModelRegistry.create({
|
|
@@ -1155,7 +1399,9 @@ export const AiModelDisplayConfig: React.FC<AiConfigProps> = ({
|
|
|
1155
1399
|
? currentDisplayedModels.filter((id) => id !== modelId)
|
|
1156
1400
|
: [...currentDisplayedModels, modelId];
|
|
1157
1401
|
|
|
1158
|
-
form.setValue("ai.models.displayed_models", newModels
|
|
1402
|
+
form.setValue("ai.models.displayed_models", newModels, {
|
|
1403
|
+
shouldDirty: true,
|
|
1404
|
+
});
|
|
1159
1405
|
onSubmit(form.getValues());
|
|
1160
1406
|
});
|
|
1161
1407
|
|
|
@@ -1172,14 +1418,18 @@ export const AiModelDisplayConfig: React.FC<AiConfigProps> = ({
|
|
|
1172
1418
|
? [...new Set([...currentDisplayedModels, ...qualifiedModelIds])]
|
|
1173
1419
|
: currentDisplayedModels.filter((id) => !qualifiedModelIds.has(id));
|
|
1174
1420
|
|
|
1175
|
-
form.setValue("ai.models.displayed_models", newModels
|
|
1421
|
+
form.setValue("ai.models.displayed_models", newModels, {
|
|
1422
|
+
shouldDirty: true,
|
|
1423
|
+
});
|
|
1176
1424
|
onSubmit(form.getValues());
|
|
1177
1425
|
},
|
|
1178
1426
|
);
|
|
1179
1427
|
|
|
1180
1428
|
const deleteModel = useEvent((modelId: QualifiedModelId) => {
|
|
1181
1429
|
const newModels = customModels.filter((id) => id !== modelId);
|
|
1182
|
-
form.setValue("ai.models.custom_models", newModels
|
|
1430
|
+
form.setValue("ai.models.custom_models", newModels, {
|
|
1431
|
+
shouldDirty: true,
|
|
1432
|
+
});
|
|
1183
1433
|
onSubmit(form.getValues());
|
|
1184
1434
|
});
|
|
1185
1435
|
|
|
@@ -1212,6 +1462,7 @@ export const AiModelDisplayConfig: React.FC<AiConfigProps> = ({
|
|
|
1212
1462
|
<AddModelForm
|
|
1213
1463
|
form={form}
|
|
1214
1464
|
customModels={customModels}
|
|
1465
|
+
customProviderNames={customProviderNames}
|
|
1215
1466
|
onSubmit={onSubmit}
|
|
1216
1467
|
/>
|
|
1217
1468
|
</SettingGroup>
|
|
@@ -1221,8 +1472,9 @@ export const AiModelDisplayConfig: React.FC<AiConfigProps> = ({
|
|
|
1221
1472
|
export const AddModelForm: React.FC<{
|
|
1222
1473
|
form: UseFormReturn<UserConfig>;
|
|
1223
1474
|
customModels: QualifiedModelId[];
|
|
1475
|
+
customProviderNames: string[];
|
|
1224
1476
|
onSubmit: (values: UserConfig) => void;
|
|
1225
|
-
}> = ({ form, customModels, onSubmit }) => {
|
|
1477
|
+
}> = ({ form, customModels, customProviderNames, onSubmit }) => {
|
|
1226
1478
|
const [isFormOpen, setIsFormOpen] = useState(false);
|
|
1227
1479
|
const [modelAdded, setModelAdded] = useState(false);
|
|
1228
1480
|
const [provider, setProvider] = useState<ProviderId | "custom" | null>(null);
|
|
@@ -1254,7 +1506,9 @@ export const AddModelForm: React.FC<{
|
|
|
1254
1506
|
modelName as ShortModelId,
|
|
1255
1507
|
);
|
|
1256
1508
|
|
|
1257
|
-
form.setValue("ai.models.custom_models", [newModel.id, ...customModels]
|
|
1509
|
+
form.setValue("ai.models.custom_models", [newModel.id, ...customModels], {
|
|
1510
|
+
shouldDirty: true,
|
|
1511
|
+
});
|
|
1258
1512
|
onSubmit(form.getValues());
|
|
1259
1513
|
resetForm();
|
|
1260
1514
|
|
|
@@ -1293,23 +1547,46 @@ export const AddModelForm: React.FC<{
|
|
|
1293
1547
|
</SelectTrigger>
|
|
1294
1548
|
<SelectContent>
|
|
1295
1549
|
<SelectGroup>
|
|
1550
|
+
{customProviderNames.length > 0 && (
|
|
1551
|
+
<>
|
|
1552
|
+
<p className="px-2 py-1 text-xs text-muted-secondary font-medium">
|
|
1553
|
+
Custom Providers
|
|
1554
|
+
</p>
|
|
1555
|
+
{customProviderNames.map((p) => (
|
|
1556
|
+
<SelectItem key={p} value={p}>
|
|
1557
|
+
<div className="flex items-center gap-2">
|
|
1558
|
+
<AiProviderIcon provider={p} className="h-4 w-4" />
|
|
1559
|
+
<span>{Strings.startCase(p)}</span>
|
|
1560
|
+
</div>
|
|
1561
|
+
</SelectItem>
|
|
1562
|
+
))}
|
|
1563
|
+
<p className="px-2 py-1 text-xs text-muted-secondary font-medium mt-1">
|
|
1564
|
+
Built-in Providers
|
|
1565
|
+
</p>
|
|
1566
|
+
</>
|
|
1567
|
+
)}
|
|
1568
|
+
{KNOWN_PROVIDERS.filter(
|
|
1569
|
+
(p) => p !== "marimo" && !customProviderNames.includes(p),
|
|
1570
|
+
).map((p) => (
|
|
1571
|
+
<SelectItem key={p} value={p}>
|
|
1572
|
+
<div className="flex items-center gap-2">
|
|
1573
|
+
<AiProviderIcon provider={p} className="h-4 w-4" />
|
|
1574
|
+
<span>{getProviderLabel(p)}</span>
|
|
1575
|
+
</div>
|
|
1576
|
+
</SelectItem>
|
|
1577
|
+
))}
|
|
1578
|
+
<p className="px-2 py-1 text-xs text-muted-secondary font-medium mt-1">
|
|
1579
|
+
Other
|
|
1580
|
+
</p>
|
|
1296
1581
|
<SelectItem value="custom">
|
|
1297
1582
|
<div className="flex items-center gap-2">
|
|
1298
1583
|
<AiProviderIcon
|
|
1299
1584
|
provider="openai-compatible"
|
|
1300
1585
|
className="h-4 w-4"
|
|
1301
1586
|
/>
|
|
1302
|
-
<span>
|
|
1587
|
+
<span>Enter provider name</span>
|
|
1303
1588
|
</div>
|
|
1304
1589
|
</SelectItem>
|
|
1305
|
-
{PROVIDERS.filter((p) => p !== "marimo").map((p) => (
|
|
1306
|
-
<SelectItem key={p} value={p}>
|
|
1307
|
-
<div className="flex items-center gap-2">
|
|
1308
|
-
<AiProviderIcon provider={p} className="h-4 w-4" />
|
|
1309
|
-
<span>{getProviderLabel(p)}</span>
|
|
1310
|
-
</div>
|
|
1311
|
-
</SelectItem>
|
|
1312
|
-
))}
|
|
1313
1590
|
</SelectGroup>
|
|
1314
1591
|
</SelectContent>
|
|
1315
1592
|
</Select>
|
|
@@ -1379,17 +1656,12 @@ export const AddModelForm: React.FC<{
|
|
|
1379
1656
|
<div>
|
|
1380
1657
|
{isFormOpen && inputForm}
|
|
1381
1658
|
<div className="flex flex-row text-sm">
|
|
1382
|
-
<
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
disabled={isFormOpen}
|
|
1389
|
-
>
|
|
1390
|
-
<PlusIcon className="h-4 w-4 mr-2 mb-0.5" />
|
|
1391
|
-
Add Model
|
|
1392
|
-
</Button>
|
|
1659
|
+
<AddButton
|
|
1660
|
+
isFormOpen={isFormOpen}
|
|
1661
|
+
setIsFormOpen={setIsFormOpen}
|
|
1662
|
+
label="Add Model"
|
|
1663
|
+
className="pl-2"
|
|
1664
|
+
/>
|
|
1393
1665
|
{modelAdded && (
|
|
1394
1666
|
<div className="flex items-center gap-1 text-green-700 bg-green-500/10 px-2 py-1 rounded-md ml-auto">
|
|
1395
1667
|
✓ Model added
|
|
@@ -1400,6 +1672,34 @@ export const AddModelForm: React.FC<{
|
|
|
1400
1672
|
);
|
|
1401
1673
|
};
|
|
1402
1674
|
|
|
1675
|
+
const AddButton = ({
|
|
1676
|
+
isFormOpen,
|
|
1677
|
+
setIsFormOpen,
|
|
1678
|
+
label,
|
|
1679
|
+
className,
|
|
1680
|
+
}: {
|
|
1681
|
+
isFormOpen: boolean;
|
|
1682
|
+
setIsFormOpen: (isOpen: boolean) => void;
|
|
1683
|
+
label: string;
|
|
1684
|
+
className?: string;
|
|
1685
|
+
}) => {
|
|
1686
|
+
return (
|
|
1687
|
+
<Button
|
|
1688
|
+
onClick={(e) => {
|
|
1689
|
+
e.stopPropagation();
|
|
1690
|
+
e.preventDefault();
|
|
1691
|
+
setIsFormOpen(true);
|
|
1692
|
+
}}
|
|
1693
|
+
variant="link"
|
|
1694
|
+
disabled={isFormOpen}
|
|
1695
|
+
className={cn("px-0", className)}
|
|
1696
|
+
>
|
|
1697
|
+
<PlusIcon className="h-4 w-4 mr-2 mb-0.5" />
|
|
1698
|
+
{label}
|
|
1699
|
+
</Button>
|
|
1700
|
+
);
|
|
1701
|
+
};
|
|
1702
|
+
|
|
1403
1703
|
export const AiConfig: React.FC<AiConfigProps> = ({
|
|
1404
1704
|
form,
|
|
1405
1705
|
config,
|
|
@@ -69,14 +69,21 @@ export function getDirtyValues<T extends FieldValues>(
|
|
|
69
69
|
dirtyFields: Partial<Record<keyof T, unknown>>,
|
|
70
70
|
): Partial<T> {
|
|
71
71
|
const result: Partial<T> = {};
|
|
72
|
-
for (const key of Object.keys(dirtyFields) as
|
|
72
|
+
for (const key of Object.keys(dirtyFields) as (keyof T)[]) {
|
|
73
73
|
const dirty = dirtyFields[key];
|
|
74
|
+
const value = values[key];
|
|
75
|
+
|
|
76
|
+
// Skip if the value no longer exists (e.g., deleted from a record)
|
|
77
|
+
if (value === undefined) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
if (dirty === true) {
|
|
75
|
-
result[key] =
|
|
82
|
+
result[key] = value;
|
|
76
83
|
} else if (typeof dirty === "object" && dirty !== null) {
|
|
77
84
|
// Nested object - recurse
|
|
78
85
|
const nested = getDirtyValues(
|
|
79
|
-
|
|
86
|
+
value as FieldValues,
|
|
80
87
|
dirty as Partial<Record<string, unknown>>,
|
|
81
88
|
);
|
|
82
89
|
if (Object.keys(nested).length > 0) {
|
|
@@ -677,7 +677,7 @@ const AgentPanel: React.FC = () => {
|
|
|
677
677
|
? getAgentWebSocketUrl(selectedTab.agentId)
|
|
678
678
|
: NO_WS_SET;
|
|
679
679
|
const { sendUpdateFile, sendFileDetails } = useRequestClient();
|
|
680
|
-
const
|
|
680
|
+
const creatingOrResumingSession = useRef(false);
|
|
681
681
|
|
|
682
682
|
const acpClient = useAcpClient({
|
|
683
683
|
wsUrl,
|
|
@@ -716,6 +716,7 @@ const AgentPanel: React.FC = () => {
|
|
|
716
716
|
sessionMode,
|
|
717
717
|
activeSessionId,
|
|
718
718
|
agent,
|
|
719
|
+
clearNotifications,
|
|
719
720
|
} = acpClient;
|
|
720
721
|
|
|
721
722
|
useEffect(() => {
|
|
@@ -757,40 +758,42 @@ const AgentPanel: React.FC = () => {
|
|
|
757
758
|
return;
|
|
758
759
|
}
|
|
759
760
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
761
|
+
creatingOrResumingSession.current = true;
|
|
762
|
+
|
|
763
|
+
try {
|
|
764
|
+
// If there is an active session, we should stop it
|
|
765
|
+
if (activeSessionId) {
|
|
766
|
+
await agent.cancel({ sessionId: activeSessionId }).catch((error) => {
|
|
767
|
+
logger.error("Failed to cancel active session", { error });
|
|
768
|
+
});
|
|
769
|
+
clearNotifications(activeSessionId);
|
|
770
|
+
setActiveSessionId(null);
|
|
771
|
+
}
|
|
767
772
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
const newSession = await agent
|
|
773
|
-
.newSession({
|
|
773
|
+
// Get the selected model from the current session state
|
|
774
|
+
const currentModel = selectedTab?.selectedModel ?? null;
|
|
775
|
+
logger.debug("Creating new agent session", { model: currentModel });
|
|
776
|
+
const newSession = await agent.newSession({
|
|
774
777
|
cwd: getCwd(),
|
|
775
778
|
mcpServers: [],
|
|
776
779
|
_meta: currentModel ? { model: currentModel } : undefined,
|
|
777
|
-
})
|
|
778
|
-
.finally(() => {
|
|
779
|
-
isCreatingNewSession.current = false;
|
|
780
780
|
});
|
|
781
781
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
782
|
+
// Capture models from the response
|
|
783
|
+
if (newSession.models) {
|
|
784
|
+
logger.debug("Session models received", { models: newSession.models });
|
|
785
|
+
setSessionModels(newSession.models);
|
|
786
|
+
}
|
|
787
787
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
788
|
+
setSessionState((prev) =>
|
|
789
|
+
updateSessionExternalAgentSessionId(
|
|
790
|
+
prev,
|
|
791
|
+
newSession.sessionId as ExternalAgentSessionId,
|
|
792
|
+
),
|
|
793
|
+
);
|
|
794
|
+
} finally {
|
|
795
|
+
creatingOrResumingSession.current = false;
|
|
796
|
+
}
|
|
794
797
|
});
|
|
795
798
|
|
|
796
799
|
const handleResumeSession = useEvent(
|
|
@@ -804,23 +807,28 @@ const AgentPanel: React.FC = () => {
|
|
|
804
807
|
if (!agent.loadSession) {
|
|
805
808
|
throw new Error("Agent does not support loading sessions");
|
|
806
809
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
// Capture models from the response if available
|
|
814
|
-
if (loadedSession?.models) {
|
|
815
|
-
logger.debug("Session models received", {
|
|
816
|
-
models: loadedSession.models,
|
|
810
|
+
creatingOrResumingSession.current = true;
|
|
811
|
+
try {
|
|
812
|
+
const loadedSession = await agent.loadSession({
|
|
813
|
+
sessionId: previousSessionId,
|
|
814
|
+
cwd: getCwd(),
|
|
815
|
+
mcpServers: [],
|
|
817
816
|
});
|
|
818
|
-
setSessionModels(loadedSession.models);
|
|
819
|
-
}
|
|
820
817
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
818
|
+
// Capture models from the response if available
|
|
819
|
+
if (loadedSession?.models) {
|
|
820
|
+
logger.debug("Session models received", {
|
|
821
|
+
models: loadedSession.models,
|
|
822
|
+
});
|
|
823
|
+
setSessionModels(loadedSession.models);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
setSessionState((prev) =>
|
|
827
|
+
updateSessionExternalAgentSessionId(prev, previousSessionId),
|
|
828
|
+
);
|
|
829
|
+
} finally {
|
|
830
|
+
creatingOrResumingSession.current = false;
|
|
831
|
+
}
|
|
824
832
|
},
|
|
825
833
|
);
|
|
826
834
|
|
|
@@ -838,6 +846,11 @@ const AgentPanel: React.FC = () => {
|
|
|
838
846
|
return;
|
|
839
847
|
}
|
|
840
848
|
|
|
849
|
+
// Prevent race conditions
|
|
850
|
+
if (creatingOrResumingSession.current) {
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
|
|
841
854
|
// If there is an available session, resume it, otherwise create a new one
|
|
842
855
|
const createOrResumeSession = async () => {
|
|
843
856
|
const availableSession = tabLastActiveSessionId ?? activeSessionId;
|
|
@@ -8,7 +8,6 @@ import type {
|
|
|
8
8
|
InvokeAiToolRequest,
|
|
9
9
|
InvokeAiToolResponse,
|
|
10
10
|
} from "@/core/network/types";
|
|
11
|
-
import type { ChatMessage } from "@/plugins/impl/chat/types";
|
|
12
11
|
import { blobToString } from "@/utils/fileToBase64";
|
|
13
12
|
import { Logger } from "@/utils/Logger";
|
|
14
13
|
import { getAICompletionBodyWithAttachments } from "../editor/ai/completion-utils";
|
|
@@ -68,7 +67,6 @@ function stringifyTextParts(parts: UIMessage["parts"]): string {
|
|
|
68
67
|
export async function buildCompletionRequestBody(
|
|
69
68
|
messages: UIMessage[],
|
|
70
69
|
): Promise<{
|
|
71
|
-
messages: ChatMessage[]; // Deprecated. TODO: Remove in the future
|
|
72
70
|
uiMessages: UIMessage[];
|
|
73
71
|
context?: (null | components["schemas"]["AiCompletionContext"]) | undefined;
|
|
74
72
|
includeOtherCode: string;
|
|
@@ -91,25 +89,8 @@ export async function buildCompletionRequestBody(
|
|
|
91
89
|
};
|
|
92
90
|
}
|
|
93
91
|
|
|
94
|
-
function toChatMessage(message: UIMessage, isLast: boolean): ChatMessage {
|
|
95
|
-
// Clone parts to avoid mutating the original message
|
|
96
|
-
const parts = [...message.parts];
|
|
97
|
-
if (isLast) {
|
|
98
|
-
parts.push(...completionBody.attachments);
|
|
99
|
-
}
|
|
100
|
-
return {
|
|
101
|
-
id: message.id,
|
|
102
|
-
role: message.role,
|
|
103
|
-
content: stringifyTextParts(message.parts), // This is no longer used in the backend
|
|
104
|
-
parts,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
92
|
return {
|
|
109
93
|
...completionBody.body,
|
|
110
|
-
messages: messages.map((m, idx) =>
|
|
111
|
-
toChatMessage(m, idx === messages.length - 1),
|
|
112
|
-
),
|
|
113
94
|
uiMessages: messages.map((m, idx) =>
|
|
114
95
|
addAttachmentsToMessage(m, idx === messages.length - 1),
|
|
115
96
|
),
|