@marimo-team/islands 0.15.2 → 0.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ConnectedDataExplorerComponent-C39nQwtD.js → ConnectedDataExplorerComponent-DfvW3rBn.js} +323 -328
- package/dist/{ImageComparisonComponent-BhkiyswP.js → ImageComparisonComponent-XaJshw7d.js} +13 -13
- package/dist/{_baseUniq-DdHL34FO.js → _baseUniq-dN9WKF9m.js} +67 -67
- package/dist/any-language-editor-CpFniVi-.js +27 -0
- package/dist/{arc-BXrety1g.js → arc-BOhn-m2C.js} +1 -1
- package/dist/{architectureDiagram-KFL7JDKH-BMy6ywCF.js → architectureDiagram-W76B3OCA-Bpg85ZKv.js} +144 -144
- package/dist/assets/{worker-COGufAQn.js → worker-Y-Q4G-N2.js} +30 -26
- package/dist/asterisk-DS281yxp.js +271 -0
- package/dist/{blockDiagram-ZYB65J3Q-DYT2-nlI.js → blockDiagram-QIGZ2CNN-DS1kOHlW.js} +10 -10
- package/dist/{c4Diagram-AAMF2YG6-ZiQzioe6.js → c4Diagram-FPNF74CW-CyRVKssw.js} +8 -8
- package/dist/{channel-CeuXqUAU.js → channel-BilGXox7.js} +1 -1
- package/dist/{chunk-ANTBXLJU-BvYnIrdq.js → chunk-4BX2VUAB-CZR39zCO.js} +1 -1
- package/dist/{chunk-WVR4S24B-DXj8yaUk.js → chunk-55IACEB6-BIH-MYov.js} +1 -1
- package/dist/{chunk-GLLZNHP4-CyFsosAe.js → chunk-FMBD7UC4-4PZXFZE8.js} +1 -1
- package/dist/{chunk-JBRWN2VN-DA_EEhy2.js → chunk-K7UQS3LO-CEvWKznk.js} +117 -117
- package/dist/{chunk-NRVI72HA-BYx2jMlI.js → chunk-QN33PNHL-D5LO5Jq_.js} +1 -1
- package/dist/{chunk-FHKO5MBM-DfCztBk8.js → chunk-QZHKN3VN-6gwUonWI.js} +1 -1
- package/dist/{chunk-LXBSTHXV-Se7vdY6J.js → chunk-TVAH2DTR-3gm06QdU.js} +7 -7
- package/dist/{chunk-OMD6QJNC-CqgcPMgL.js → chunk-TZMSLE5B-Cm8Iy9bO.js} +1 -1
- package/dist/{classDiagram-v2-QTMF73CY-B19A3G1l.js → classDiagram-KNZD7YFC-DC529O_z.js} +2 -2
- package/dist/{classDiagram-3BZAVTQC-B19A3G1l.js → classDiagram-v2-RKCZMP56-DC529O_z.js} +2 -2
- package/dist/{clone-78au0tn1.js → clone-CLoRX3j6.js} +1 -1
- package/dist/cose-bilkent-S5V4N54A-qf5DlS6Y.js +2609 -0
- package/dist/{cytoscape.esm-BYnVVhJX.js → cytoscape.esm-DfdJODL8.js} +34 -34
- package/dist/{dagre-2BBEFEWP-BfEn3ZUV.js → dagre-5GWH7T2D-Ceocls0m.js} +6 -6
- package/dist/{data-grid-overlay-editor-CH_qLkV2.js → data-grid-overlay-editor-AqDS_UKe.js} +11 -11
- package/dist/{diagram-4IRLE6MV-CL8xidnG.js → diagram-N5W7TBWH-CP66oSiv.js} +59 -60
- package/dist/{diagram-RP2FKANI-B1BPcUew.js → diagram-QEK2KX5R-_YD4kxxi.js} +15 -15
- package/dist/{diagram-GUPCWM2R-CZ5cfqlq.js → diagram-S2PKOQOG-Cnj8T-OP.js} +10 -10
- package/dist/dockerfile-Cm8cRYCN.js +194 -0
- package/dist/ebnf-DUPDuY4r.js +78 -0
- package/dist/{erDiagram-HZWUO2LU-BEAIww50.js → erDiagram-AWTI2OKA-CGnvoHx6.js} +8 -8
- package/dist/fcl-CPC2WYrI.js +103 -0
- package/dist/{flowDiagram-THRYKUMA-Czs2UAI2.js → flowDiagram-PVAE7QVJ-DG-pr9R9.js} +9 -9
- package/dist/{ganttDiagram-WV7ZQ7D5-ByYIAVFO.js → ganttDiagram-OWAHRB6G-JmChtxvn.js} +34 -34
- package/dist/{gitGraphDiagram-OJR772UL-BcpDsiyB.js → gitGraphDiagram-NY62KEGX-D8wLfOPd.js} +4 -4
- package/dist/{glide-data-editor-CmN6FVyi.js → glide-data-editor-9nC3iCIZ.js} +33 -33
- package/dist/{graph-77W6heli.js → graph-CoRe7vAN.js} +3 -3
- package/dist/http-D9LttvKF.js +44 -0
- package/dist/{index-Bfk9dnyS.js → index-6qYeHHjQ.js} +33090 -32892
- package/dist/{index-BOojn38D.js → index-BpzLh4Qe.js} +7711 -7711
- package/dist/{index-CmozKMxx.js → index-BthgsgYX.js} +6 -6
- package/dist/{index-pBmAzQJl.js → index-MCx5v1x0.js} +2 -2
- package/dist/index-jkm77Jrz.js +98 -0
- package/dist/{infoDiagram-6WOFNB3A-CfzLHHVP.js → infoDiagram-STP46IZ2-BlXxvOrR.js} +2 -2
- package/dist/{journeyDiagram-FFXJYRFH-ndAcpkGn.js → journeyDiagram-BIP6EPQ6-CNRYs_Fc.js} +24 -26
- package/dist/{kanban-definition-KOZQBZVT-DcQYzNvc.js → kanban-definition-6OIFK2YF-B9HeMAuP.js} +14 -14
- package/dist/{layout-XySVHJgD.js → layout-m2vOUiW1.js} +81 -81
- package/dist/{linear-PbooOqg7.js → linear-DU6Q5CX3.js} +35 -35
- package/dist/{main-B5yML0bw.js → main-BD2KGFpU.js} +74594 -68034
- package/dist/main.js +1 -1
- package/dist/{mermaid-Cg5IX6Nv.js → mermaid-HVCtvbyx.js} +6160 -7493
- package/dist/min-DcGMA4e_.js +80 -0
- package/dist/mindmap-definition-Q6HEUPPD-BW8UmIDQ.js +785 -0
- package/dist/nginx-zDPm3Z74.js +89 -0
- package/dist/{number-overlay-editor-DUhfZqtP.js → number-overlay-editor-D8Hl0Syo.js} +19 -19
- package/dist/{pieDiagram-DBDJKBY4-DTOlNsja.js → pieDiagram-ADFJNKIX-Bg-3zg5U.js} +17 -17
- package/dist/{quadrantDiagram-YPSRARAO-BX2d8VS-.js → quadrantDiagram-LMRXKWRM-BO4IG6Yz.js} +6 -6
- package/dist/{react-plotly-Dcyw-3Sa.js → react-plotly-dkvHVuRb.js} +3577 -3577
- package/dist/{requirementDiagram-EGVEC5DT-D1T5u-wG.js → requirementDiagram-4UW4RH46-5sdTguSM.js} +7 -7
- package/dist/{sankeyDiagram-HRAUVNP4-G6xDfnp-.js → sankeyDiagram-GR3RE2ED-Buhlv9OI.js} +5 -5
- package/dist/sequenceDiagram-C3RYC4MD-C3qsM2UP.js +2519 -0
- package/dist/{slides-component-BJLlPJSr.js → slides-component-D209A0-s.js} +66 -66
- package/dist/solr-BNlsLglM.js +41 -0
- package/dist/spreadsheet-C-cy4P5N.js +49 -0
- package/dist/{stateDiagram-UUKSUZ4H-CYXbjaom.js → stateDiagram-KXAO66HF-CopJ7G6P.js} +5 -5
- package/dist/{stateDiagram-v2-EYPG3UTE-Br1HYKT6.js → stateDiagram-v2-UMBNRL4Z-CejL8AKf.js} +2 -2
- package/dist/style.css +1 -1
- package/dist/tiddlywiki-5wqsXtSk.js +155 -0
- package/dist/tiki-__Kn3CeS.js +181 -0
- package/dist/{time-B9SZnSen.js → time-BwSBitlN.js} +58 -58
- package/dist/{timeline-definition-3HZDQTIS-DeK_ZRD0.js → timeline-definition-XQNQX7LJ-DzMNTX-C.js} +10 -12
- package/dist/{timer-BYwnU4DF.js → timer-B0-z63CM.js} +16 -16
- package/dist/{treemap-75Q7IDZK-CKP4vV_0.js → treemap-75Q7IDZK-zeJG07dk.js} +14 -14
- package/dist/{vega-component-CpgdqX2d.js → vega-component-CUkiTayd.js} +30 -30
- package/dist/{xychartDiagram-FDP5SA34-AMEPsx_R.js → xychartDiagram-6GGTOJPD-DiENNXMS.js} +7 -7
- package/package.json +39 -39
- package/src/__mocks__/notebook.ts +3 -0
- package/src/__mocks__/requests.ts +3 -0
- package/src/__tests__/__snapshots__/CellStatus.test.tsx.snap +12 -12
- package/src/__tests__/chat-utils.test.ts +26 -211
- package/src/components/ai/ai-model-dropdown.tsx +25 -9
- package/src/components/app-config/ai-config.tsx +7 -0
- package/src/components/chat/chat-components.tsx +71 -0
- package/src/components/chat/chat-panel.tsx +481 -291
- package/src/components/chat/chat-utils.ts +50 -0
- package/src/components/chat/markdown-renderer.tsx +3 -7
- package/src/components/chat/tool-call-accordion.tsx +5 -5
- package/src/components/datasources/__tests__/utils.test.ts +6 -0
- package/src/components/datasources/column-preview.tsx +1 -3
- package/src/components/editor/actions/useNotebookActions.tsx +1 -1
- package/src/components/editor/ai/add-cell-with-ai.tsx +20 -15
- package/src/components/editor/ai/ai-completion-editor.tsx +22 -3
- package/src/components/editor/ai/completion-handlers.tsx +2 -4
- package/src/components/editor/ai/completion-utils.ts +85 -11
- package/src/components/editor/alerts/startup-logs-alert.tsx +72 -0
- package/src/components/editor/chrome/panels/datasources-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/dependency-graph-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/documentation-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/error-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/file-explorer-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/logs-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/outline-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/packages-panel.tsx +4 -2
- package/src/components/editor/chrome/panels/scratchpad-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/secrets-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/snippets-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/tracing-panel.tsx +3 -1
- package/src/components/editor/chrome/panels/variable-panel.tsx +3 -1
- package/src/components/editor/chrome/wrapper/app-chrome.tsx +38 -28
- package/src/components/editor/controls/command-palette-button.tsx +1 -1
- package/src/components/editor/controls/command-palette.tsx +5 -4
- package/src/components/editor/controls/state.ts +4 -0
- package/src/components/editor/package-alert.tsx +108 -58
- package/src/components/editor/renderers/CellArray.tsx +2 -0
- package/src/components/editor/renderers/vertical-layout/vertical-layout.tsx +0 -1
- package/src/components/pages/edit-page.tsx +7 -3
- package/src/core/ai/chat-utils.ts +26 -43
- package/src/core/ai/config.ts +1 -1
- package/src/core/ai/context/__tests__/registry.test.ts +277 -3
- package/src/core/ai/context/context.ts +11 -1
- package/src/core/ai/context/providers/__tests__/cell-output.test.ts +378 -0
- package/src/core/ai/context/providers/__tests__/error.test.ts +3 -2
- package/src/core/ai/context/providers/__tests__/file.test.ts +119 -0
- package/src/core/ai/context/providers/cell-output.ts +349 -0
- package/src/core/ai/context/providers/common.ts +5 -1
- package/src/core/ai/context/providers/file.ts +287 -0
- package/src/core/ai/context/registry.ts +79 -0
- package/src/core/ai/state.ts +22 -5
- package/src/core/alerts/state.ts +71 -3
- package/src/core/cells/cell.ts +2 -2
- package/src/core/cells/cells.ts +1 -1
- package/src/core/cells/logs.ts +1 -1
- package/src/core/cells/runs.ts +6 -5
- package/src/core/codemirror/ai/resources.ts +47 -5
- package/src/core/codemirror/ai/state.ts +12 -0
- package/src/core/codemirror/language/__tests__/sql.test.ts +45 -0
- package/src/core/codemirror/markdown/__tests__/commands.test.ts +1 -0
- package/src/core/codemirror/theme/dark.ts +1 -1
- package/src/core/config/capabilities.ts +1 -1
- package/src/core/datasets/__tests__/data-source.test.ts +24 -0
- package/src/core/errors/__tests__/errors.test.ts +2 -0
- package/src/core/islands/bridge.ts +1 -0
- package/src/core/islands/main.ts +1 -0
- package/src/core/kernel/messages.ts +12 -6
- package/src/core/layout/layout.ts +3 -3
- package/src/core/network/requests-network.ts +8 -0
- package/src/core/network/requests-static.ts +1 -0
- package/src/core/network/requests-toasting.ts +1 -0
- package/src/core/network/types.ts +4 -1
- package/src/core/wasm/bridge.ts +18 -2
- package/src/core/wasm/worker/bootstrap.ts +3 -1
- package/src/core/wasm/worker/getMarimoWheel.ts +3 -8
- package/src/core/wasm/worker/types.ts +3 -0
- package/src/core/websocket/useMarimoWebSocket.tsx +7 -1
- package/src/css/app/Cell.css +42 -21
- package/src/css/app/codemirror.css +5 -1
- package/src/css/globals.css +3 -0
- package/src/css/md.css +1 -1
- package/src/plugins/impl/MicrophonePlugin.tsx +2 -2
- package/src/plugins/impl/chat/ChatPlugin.tsx +2 -9
- package/src/plugins/impl/chat/chat-ui.tsx +129 -110
- package/src/plugins/impl/chat/types.ts +5 -8
- package/src/plugins/impl/code/__tests__/language.test.ts +15 -0
- package/src/plugins/impl/code/any-language-editor.tsx +11 -8
- package/src/plugins/layout/MimeRenderPlugin.tsx +3 -6
- package/src/stories/cell.stories.tsx +6 -0
- package/src/stories/layout/vertical/one-column.stories.tsx +215 -0
- package/src/theme/useTheme.ts +11 -6
- package/src/utils/__tests__/blob.test.ts +37 -0
- package/src/utils/arrays.ts +13 -0
- package/src/utils/fileToBase64.ts +21 -6
- package/src/utils/json/base64.ts +5 -2
- package/src/utils/numbers.ts +9 -7
- package/dist/any-language-editor-DC5170DQ.js +0 -45
- package/dist/asn1-jKiBa2Ya.js +0 -95
- package/dist/clojure-CCKyeQKf.js +0 -800
- package/dist/css-BkF-NPzE.js +0 -1553
- package/dist/index-5ZH_qS8j.js +0 -288
- package/dist/index-U4yn89qO.js +0 -341
- package/dist/javascript-C2yteZeJ.js +0 -691
- package/dist/min-DS5Jz-hg.js +0 -80
- package/dist/mindmap-definition-LNHGMQRG-0aOVaMR8.js +0 -3234
- package/dist/mllike-BSnXJBGA.js +0 -272
- package/dist/pug-CwAQJzGR.js +0 -248
- package/dist/python-BkR3uSy8.js +0 -313
- package/dist/rpm-IznJm2Xc.js +0 -57
- package/dist/sequenceDiagram-WFGC7UMF-DMhHzllb.js +0 -2284
- package/dist/ttcn-cfg-Bac_acMi.js +0 -88
|
@@ -104,12 +104,14 @@ export const MockNotebook = {
|
|
|
104
104
|
type: "exception",
|
|
105
105
|
msg,
|
|
106
106
|
exception_type,
|
|
107
|
+
raising_cell: null,
|
|
107
108
|
}),
|
|
108
109
|
|
|
109
110
|
strictException: (msg: string, ref: string): MarimoError => ({
|
|
110
111
|
type: "strict-exception",
|
|
111
112
|
msg,
|
|
112
113
|
ref,
|
|
114
|
+
blamed_cell: null,
|
|
113
115
|
}),
|
|
114
116
|
|
|
115
117
|
interruption: (): MarimoError => ({
|
|
@@ -124,6 +126,7 @@ export const MockNotebook = {
|
|
|
124
126
|
unknown: (msg: string): MarimoError => ({
|
|
125
127
|
type: "unknown",
|
|
126
128
|
msg,
|
|
129
|
+
error_type: null,
|
|
127
130
|
}),
|
|
128
131
|
},
|
|
129
132
|
|
|
@@ -44,6 +44,9 @@ export const MockRequestClient = {
|
|
|
44
44
|
getUsageStats: vi.fn().mockResolvedValue({}),
|
|
45
45
|
sendPdb: vi.fn().mockResolvedValue({}),
|
|
46
46
|
sendListFiles: vi.fn().mockResolvedValue({ files: [] }),
|
|
47
|
+
sendSearchFiles: vi
|
|
48
|
+
.fn()
|
|
49
|
+
.mockResolvedValue({ files: [], query: "", total_found: 0 }),
|
|
47
50
|
sendCreateFileOrFolder: vi.fn().mockResolvedValue({}),
|
|
48
51
|
sendDeleteFileOrFolder: vi.fn().mockResolvedValue({}),
|
|
49
52
|
sendRenameFileOrFolder: vi.fn().mockResolvedValue({}),
|
|
@@ -39,14 +39,14 @@ exports[`CellStatusComponent > renders disabled and stale state 1`] = `
|
|
|
39
39
|
width="24"
|
|
40
40
|
xmlns="http://www.w3.org/2000/svg"
|
|
41
41
|
>
|
|
42
|
+
<path
|
|
43
|
+
d="M4.929 4.929 19.07 19.071"
|
|
44
|
+
/>
|
|
42
45
|
<circle
|
|
43
46
|
cx="12"
|
|
44
47
|
cy="12"
|
|
45
48
|
r="10"
|
|
46
49
|
/>
|
|
47
|
-
<path
|
|
48
|
-
d="m4.9 4.9 14.2 14.2"
|
|
49
|
-
/>
|
|
50
50
|
</svg>
|
|
51
51
|
<div
|
|
52
52
|
class="second-icon absolute bottom-[-2px] right-[-2px] rounded-full"
|
|
@@ -104,14 +104,14 @@ exports[`CellStatusComponent > renders disabled state 1`] = `
|
|
|
104
104
|
width="24"
|
|
105
105
|
xmlns="http://www.w3.org/2000/svg"
|
|
106
106
|
>
|
|
107
|
+
<path
|
|
108
|
+
d="M4.929 4.929 19.07 19.071"
|
|
109
|
+
/>
|
|
107
110
|
<circle
|
|
108
111
|
cx="12"
|
|
109
112
|
cy="12"
|
|
110
113
|
r="10"
|
|
111
114
|
/>
|
|
112
|
-
<path
|
|
113
|
-
d="m4.9 4.9 14.2 14.2"
|
|
114
|
-
/>
|
|
115
115
|
</svg>
|
|
116
116
|
</div>
|
|
117
117
|
</div>
|
|
@@ -175,14 +175,14 @@ exports[`CellStatusComponent > renders disabled transitively state 1`] = `
|
|
|
175
175
|
width="24"
|
|
176
176
|
xmlns="http://www.w3.org/2000/svg"
|
|
177
177
|
>
|
|
178
|
+
<path
|
|
179
|
+
d="M4.929 4.929 19.07 19.071"
|
|
180
|
+
/>
|
|
178
181
|
<circle
|
|
179
182
|
cx="12"
|
|
180
183
|
cy="12"
|
|
181
184
|
r="10"
|
|
182
185
|
/>
|
|
183
|
-
<path
|
|
184
|
-
d="m4.9 4.9 14.2 14.2"
|
|
185
|
-
/>
|
|
186
186
|
</svg>
|
|
187
187
|
</div>
|
|
188
188
|
</div>
|
|
@@ -403,14 +403,14 @@ exports[`CellStatusComponent > renders stale and disabled transitively state 1`]
|
|
|
403
403
|
width="24"
|
|
404
404
|
xmlns="http://www.w3.org/2000/svg"
|
|
405
405
|
>
|
|
406
|
+
<path
|
|
407
|
+
d="M4.929 4.929 19.07 19.071"
|
|
408
|
+
/>
|
|
406
409
|
<circle
|
|
407
410
|
cx="12"
|
|
408
411
|
cy="12"
|
|
409
412
|
r="10"
|
|
410
413
|
/>
|
|
411
|
-
<path
|
|
412
|
-
d="m4.9 4.9 14.2 14.2"
|
|
413
|
-
/>
|
|
414
414
|
</svg>
|
|
415
415
|
</div>
|
|
416
416
|
</div>
|
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
2
|
|
|
3
|
+
import type { UIMessage } from "ai";
|
|
3
4
|
import { describe, expect, it } from "vitest";
|
|
4
5
|
import { Maps } from "@/utils/maps";
|
|
5
|
-
import {
|
|
6
|
+
import { replaceMessagesInChat } from "../core/ai/chat-utils";
|
|
6
7
|
import type { Chat, ChatId, ChatState } from "../core/ai/state";
|
|
7
8
|
|
|
8
9
|
const CHAT_1 = "chat-1" as ChatId;
|
|
9
|
-
const CHAT_2 = "chat-2" as ChatId;
|
|
10
|
-
|
|
11
|
-
function first(map: Map<ChatId, Chat>) {
|
|
12
|
-
return [...map.values()][0];
|
|
13
|
-
}
|
|
14
10
|
|
|
15
11
|
function asMap(list: Iterable<Chat>) {
|
|
16
12
|
return Maps.keyBy(list, (c) => c.id);
|
|
17
13
|
}
|
|
18
|
-
|
|
19
|
-
describe("addMessageToChat", () => {
|
|
14
|
+
describe("replaceMessagesInChat", () => {
|
|
20
15
|
const mockChatState: ChatState = {
|
|
21
16
|
chats: asMap([
|
|
22
17
|
{
|
|
@@ -26,223 +21,43 @@ describe("addMessageToChat", () => {
|
|
|
26
21
|
{
|
|
27
22
|
id: "msg-1",
|
|
28
23
|
role: "user",
|
|
29
|
-
|
|
30
|
-
timestamp: 1000,
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
id: "msg-2",
|
|
34
|
-
role: "assistant",
|
|
35
|
-
content: "Hi there!",
|
|
36
|
-
timestamp: 2000,
|
|
24
|
+
parts: [{ type: "text", text: "Hello" }],
|
|
25
|
+
metadata: { timestamp: 1000 },
|
|
37
26
|
},
|
|
38
27
|
],
|
|
39
28
|
createdAt: 1000,
|
|
40
29
|
updatedAt: 2000,
|
|
41
30
|
},
|
|
42
|
-
{
|
|
43
|
-
id: CHAT_2,
|
|
44
|
-
title: "Test Chat 2",
|
|
45
|
-
messages: [
|
|
46
|
-
{
|
|
47
|
-
id: "msg-3",
|
|
48
|
-
role: "user",
|
|
49
|
-
content: "How are you?",
|
|
50
|
-
timestamp: 3000,
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
createdAt: 3000,
|
|
54
|
-
updatedAt: 3000,
|
|
55
|
-
},
|
|
56
31
|
]),
|
|
57
32
|
activeChatId: CHAT_1,
|
|
58
33
|
};
|
|
59
34
|
|
|
60
|
-
it("
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
id: "msg-4",
|
|
74
|
-
role: "user",
|
|
75
|
-
content: "New message",
|
|
76
|
-
timestamp: expect.any(Number),
|
|
35
|
+
it("replaces messages in a chat", () => {
|
|
36
|
+
const newMessages: UIMessage[] = [
|
|
37
|
+
{
|
|
38
|
+
id: "msg-2",
|
|
39
|
+
role: "assistant",
|
|
40
|
+
parts: [{ type: "text", text: "Hi there!" }],
|
|
41
|
+
metadata: { timestamp: 2000 },
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
const result = replaceMessagesInChat({
|
|
45
|
+
chatState: mockChatState,
|
|
46
|
+
chatId: CHAT_1,
|
|
47
|
+
messages: newMessages,
|
|
77
48
|
});
|
|
78
|
-
expect(
|
|
79
|
-
|
|
49
|
+
expect(result.chats.get(CHAT_1)?.messages).toEqual(newMessages);
|
|
50
|
+
expect(result.chats.get(CHAT_1)?.updatedAt).toBeGreaterThan(
|
|
51
|
+
mockChatState.chats.get(CHAT_1)?.updatedAt ?? 0,
|
|
80
52
|
);
|
|
81
53
|
});
|
|
82
54
|
|
|
83
|
-
it("
|
|
84
|
-
const result =
|
|
85
|
-
mockChatState,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
"user",
|
|
89
|
-
"Updated content",
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
expect(result.chats).toHaveLength(2);
|
|
93
|
-
const updatedChat = result.chats.get(CHAT_1);
|
|
94
|
-
expect(updatedChat?.messages).toHaveLength(2);
|
|
95
|
-
expect(updatedChat?.messages[0]).toEqual({
|
|
96
|
-
id: "msg-1",
|
|
97
|
-
role: "user",
|
|
98
|
-
content: "Updated content",
|
|
99
|
-
timestamp: 1000,
|
|
55
|
+
it("returns unchanged state if chatId is null", () => {
|
|
56
|
+
const result = replaceMessagesInChat({
|
|
57
|
+
chatState: mockChatState,
|
|
58
|
+
chatId: null,
|
|
59
|
+
messages: [],
|
|
100
60
|
});
|
|
101
|
-
expect(updatedChat?.updatedAt).toBeGreaterThan(
|
|
102
|
-
first(mockChatState.chats).updatedAt,
|
|
103
|
-
);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
it("should handle message parts", () => {
|
|
107
|
-
const parts = [{ type: "text" as const, text: "Part content" }];
|
|
108
|
-
const result = addMessageToChat(
|
|
109
|
-
mockChatState,
|
|
110
|
-
CHAT_1,
|
|
111
|
-
"msg-5",
|
|
112
|
-
"assistant",
|
|
113
|
-
"Message with parts",
|
|
114
|
-
parts,
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
const updatedChat = result.chats.get(CHAT_1);
|
|
118
|
-
expect(updatedChat?.messages[2].parts).toEqual(parts);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("should update message parts", () => {
|
|
122
|
-
const originalParts = [{ type: "text" as const, text: "Original" }];
|
|
123
|
-
const updatedParts = [{ type: "text" as const, text: "Updated" }];
|
|
124
|
-
const chats = [...mockChatState.chats.values()];
|
|
125
|
-
|
|
126
|
-
const stateWithParts: ChatState = {
|
|
127
|
-
...mockChatState,
|
|
128
|
-
chats: asMap([
|
|
129
|
-
{
|
|
130
|
-
...chats[0],
|
|
131
|
-
messages: [
|
|
132
|
-
{
|
|
133
|
-
...chats[0].messages[0],
|
|
134
|
-
parts: originalParts,
|
|
135
|
-
},
|
|
136
|
-
chats[0].messages[1],
|
|
137
|
-
],
|
|
138
|
-
},
|
|
139
|
-
chats[1],
|
|
140
|
-
]),
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const result = addMessageToChat(
|
|
144
|
-
stateWithParts,
|
|
145
|
-
CHAT_1,
|
|
146
|
-
"msg-1",
|
|
147
|
-
"user",
|
|
148
|
-
"Updated content",
|
|
149
|
-
updatedParts,
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
const updatedChat = result.chats.get(CHAT_1);
|
|
153
|
-
expect(updatedChat?.messages[0].parts).toEqual(updatedParts);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("should return unchanged state when chatId is null", () => {
|
|
157
|
-
const result = addMessageToChat(
|
|
158
|
-
mockChatState,
|
|
159
|
-
null,
|
|
160
|
-
"msg-4",
|
|
161
|
-
"user",
|
|
162
|
-
"New message",
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
expect(result).toEqual(mockChatState);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("should return unchanged state when chatId does not exist", () => {
|
|
169
|
-
const result = addMessageToChat(
|
|
170
|
-
mockChatState,
|
|
171
|
-
"non-existent-chat" as ChatId,
|
|
172
|
-
"msg-4",
|
|
173
|
-
"user",
|
|
174
|
-
"New message",
|
|
175
|
-
);
|
|
176
|
-
|
|
177
61
|
expect(result).toEqual(mockChatState);
|
|
178
62
|
});
|
|
179
|
-
|
|
180
|
-
it("should not modify other chats when updating a specific chat", () => {
|
|
181
|
-
const result = addMessageToChat(
|
|
182
|
-
mockChatState,
|
|
183
|
-
CHAT_1,
|
|
184
|
-
"msg-4",
|
|
185
|
-
"user",
|
|
186
|
-
"New message",
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const unchangedChat = result.chats.get(CHAT_2);
|
|
190
|
-
expect(unchangedChat).toEqual([...mockChatState.chats.values()][1]);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("should preserve message order when adding new messages", () => {
|
|
194
|
-
const result = addMessageToChat(
|
|
195
|
-
mockChatState,
|
|
196
|
-
CHAT_1,
|
|
197
|
-
"msg-4",
|
|
198
|
-
"user",
|
|
199
|
-
"New message",
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
const updatedChat = result.chats.get(CHAT_1);
|
|
203
|
-
expect(updatedChat?.messages[0].id).toBe("msg-1");
|
|
204
|
-
expect(updatedChat?.messages[1].id).toBe("msg-2");
|
|
205
|
-
expect(updatedChat?.messages[2].id).toBe("msg-4");
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("should handle empty chat messages array", () => {
|
|
209
|
-
const chatId = "empty-chat" as ChatId;
|
|
210
|
-
const emptyChatState: ChatState = {
|
|
211
|
-
chats: asMap([
|
|
212
|
-
{
|
|
213
|
-
id: chatId,
|
|
214
|
-
title: "Empty Chat",
|
|
215
|
-
messages: [],
|
|
216
|
-
createdAt: 1000,
|
|
217
|
-
updatedAt: 1000,
|
|
218
|
-
},
|
|
219
|
-
]),
|
|
220
|
-
activeChatId: chatId,
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
const result = addMessageToChat(
|
|
224
|
-
emptyChatState,
|
|
225
|
-
chatId,
|
|
226
|
-
"msg-1",
|
|
227
|
-
"user",
|
|
228
|
-
"First message",
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
const updatedChat = result.chats.get(chatId);
|
|
232
|
-
expect(updatedChat?.messages).toHaveLength(1);
|
|
233
|
-
expect(updatedChat?.messages[0].content).toBe("First message");
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it("should handle different message roles", () => {
|
|
237
|
-
const result = addMessageToChat(
|
|
238
|
-
mockChatState,
|
|
239
|
-
CHAT_1,
|
|
240
|
-
"msg-4",
|
|
241
|
-
"assistant",
|
|
242
|
-
"Assistant response",
|
|
243
|
-
);
|
|
244
|
-
|
|
245
|
-
const updatedChat = result.chats.get(CHAT_1);
|
|
246
|
-
expect(updatedChat?.messages[2].role).toBe("assistant");
|
|
247
|
-
});
|
|
248
63
|
});
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
CircleHelpIcon,
|
|
11
11
|
} from "lucide-react";
|
|
12
12
|
import React from "react";
|
|
13
|
+
import { type SupportedRole, useModelChange } from "@/core/ai/config";
|
|
13
14
|
import {
|
|
14
15
|
AiModelId,
|
|
15
16
|
isKnownAIProvider,
|
|
@@ -36,12 +37,13 @@ import { getCurrentRoleTooltip, getTagColour } from "./display-helpers";
|
|
|
36
37
|
interface AIModelDropdownProps {
|
|
37
38
|
value?: string;
|
|
38
39
|
placeholder?: string;
|
|
39
|
-
onSelect
|
|
40
|
+
onSelect?: (modelId: QualifiedModelId) => void;
|
|
40
41
|
triggerClassName?: string;
|
|
41
42
|
customDropdownContent?: React.ReactNode;
|
|
42
43
|
iconSize?: "medium" | "small";
|
|
43
44
|
showAddCustomModelDocs?: boolean;
|
|
44
|
-
|
|
45
|
+
displayIconOnly?: boolean;
|
|
46
|
+
forRole: SupportedRole;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
export const AIModelDropdown = ({
|
|
@@ -53,12 +55,13 @@ export const AIModelDropdown = ({
|
|
|
53
55
|
iconSize = "medium",
|
|
54
56
|
showAddCustomModelDocs = false,
|
|
55
57
|
forRole,
|
|
58
|
+
displayIconOnly = false,
|
|
56
59
|
}: AIModelDropdownProps) => {
|
|
57
|
-
const currentValue = value ? AiModelId.parse(value) : undefined;
|
|
58
60
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
59
61
|
|
|
60
62
|
const ai = useAtomValue(aiAtom);
|
|
61
63
|
const completion = useAtomValue(completionAtom);
|
|
64
|
+
const { saveModelChange } = useModelChange();
|
|
62
65
|
|
|
63
66
|
// Only include autocompleteModel if copilot is set to "custom"
|
|
64
67
|
const autocompleteModel =
|
|
@@ -88,6 +91,13 @@ export const AIModelDropdown = ({
|
|
|
88
91
|
? ai?.models?.edit_model
|
|
89
92
|
: undefined;
|
|
90
93
|
|
|
94
|
+
// If value is provided, use it, otherwise use the active model
|
|
95
|
+
const currentValue = value
|
|
96
|
+
? AiModelId.parse(value)
|
|
97
|
+
: activeModel
|
|
98
|
+
? AiModelId.parse(activeModel)
|
|
99
|
+
: undefined;
|
|
100
|
+
|
|
91
101
|
const iconSizeClass = iconSize === "medium" ? "h-4 w-4" : "h-3 w-3";
|
|
92
102
|
|
|
93
103
|
const renderModelWithRole = (modelId: AiModelId, role: Role) => {
|
|
@@ -119,7 +129,11 @@ export const AIModelDropdown = ({
|
|
|
119
129
|
};
|
|
120
130
|
|
|
121
131
|
const handleSelect = (modelId: QualifiedModelId) => {
|
|
122
|
-
onSelect
|
|
132
|
+
if (onSelect) {
|
|
133
|
+
onSelect(modelId);
|
|
134
|
+
} else {
|
|
135
|
+
saveModelChange(modelId, forRole);
|
|
136
|
+
}
|
|
123
137
|
setIsOpen(false);
|
|
124
138
|
};
|
|
125
139
|
|
|
@@ -136,11 +150,13 @@ export const AIModelDropdown = ({
|
|
|
136
150
|
provider={currentValue.providerId}
|
|
137
151
|
className={iconSizeClass}
|
|
138
152
|
/>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
153
|
+
{displayIconOnly ? null : (
|
|
154
|
+
<span className="truncate">
|
|
155
|
+
{isKnownAIProvider(currentValue.providerId)
|
|
156
|
+
? currentValue.shortModelId
|
|
157
|
+
: currentValue.id}
|
|
158
|
+
</span>
|
|
159
|
+
)}
|
|
144
160
|
</>
|
|
145
161
|
) : (
|
|
146
162
|
<span className="text-muted-foreground truncate">
|
|
@@ -31,6 +31,7 @@ import { Input } from "@/components/ui/input";
|
|
|
31
31
|
import { Kbd } from "@/components/ui/kbd";
|
|
32
32
|
import { NativeSelect } from "@/components/ui/native-select";
|
|
33
33
|
import { Textarea } from "@/components/ui/textarea";
|
|
34
|
+
import type { SupportedRole } from "@/core/ai/config";
|
|
34
35
|
import {
|
|
35
36
|
AiModelId,
|
|
36
37
|
PROVIDERS,
|
|
@@ -225,6 +226,7 @@ interface ModelSelectorProps {
|
|
|
225
226
|
description?: React.ReactNode;
|
|
226
227
|
disabled?: boolean;
|
|
227
228
|
label: string;
|
|
229
|
+
forRole: SupportedRole;
|
|
228
230
|
onSubmit: (values: UserConfig) => void;
|
|
229
231
|
}
|
|
230
232
|
|
|
@@ -237,6 +239,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|
|
237
239
|
description,
|
|
238
240
|
disabled = false,
|
|
239
241
|
label,
|
|
242
|
+
forRole,
|
|
240
243
|
onSubmit,
|
|
241
244
|
}) => {
|
|
242
245
|
return (
|
|
@@ -289,6 +292,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|
|
289
292
|
</div>
|
|
290
293
|
</>
|
|
291
294
|
}
|
|
295
|
+
forRole={forRole}
|
|
292
296
|
/>
|
|
293
297
|
</FormControl>
|
|
294
298
|
<FormMessage />
|
|
@@ -420,6 +424,7 @@ const renderCopilotProvider = ({
|
|
|
420
424
|
testId="custom-model-input"
|
|
421
425
|
description="Model to use for code completion when using a custom provider."
|
|
422
426
|
onSubmit={onSubmit}
|
|
427
|
+
forRole="autocomplete"
|
|
423
428
|
/>
|
|
424
429
|
);
|
|
425
430
|
}
|
|
@@ -905,6 +910,7 @@ export const AiAssistConfig: React.FC<AiConfigProps> = ({
|
|
|
905
910
|
description={
|
|
906
911
|
<span>Model to use for chat conversations in the Chat panel.</span>
|
|
907
912
|
}
|
|
913
|
+
forRole="chat"
|
|
908
914
|
onSubmit={onSubmit}
|
|
909
915
|
/>
|
|
910
916
|
<ModelSelector
|
|
@@ -921,6 +927,7 @@ export const AiAssistConfig: React.FC<AiConfigProps> = ({
|
|
|
921
927
|
<Kbd className="inline">Generate with AI</Kbd> button.
|
|
922
928
|
</span>
|
|
923
929
|
}
|
|
930
|
+
forRole="edit"
|
|
924
931
|
onSubmit={onSubmit}
|
|
925
932
|
/>
|
|
926
933
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* Copyright 2024 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { FileUIPart } from "ai";
|
|
4
|
+
import { FileIcon, FileTextIcon, ImageIcon, XIcon } from "lucide-react";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { cn } from "@/utils/cn";
|
|
7
|
+
|
|
8
|
+
export const AttachmentRenderer = ({
|
|
9
|
+
attachment,
|
|
10
|
+
}: {
|
|
11
|
+
attachment: FileUIPart;
|
|
12
|
+
}) => {
|
|
13
|
+
if (attachment.mediaType?.startsWith("image/")) {
|
|
14
|
+
return (
|
|
15
|
+
<img
|
|
16
|
+
src={attachment.url}
|
|
17
|
+
alt={attachment.filename}
|
|
18
|
+
className="max-h-[100px] max-w-[100px] object-contain mb-1.5"
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="flex flex-row gap-1 items-center text-xs">
|
|
25
|
+
<FileIcon className="h-3 w-3 mt-0.5" />
|
|
26
|
+
{attachment.filename}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const FileAttachmentPill = ({
|
|
32
|
+
file,
|
|
33
|
+
className,
|
|
34
|
+
onRemove,
|
|
35
|
+
}: {
|
|
36
|
+
file: File;
|
|
37
|
+
className?: string;
|
|
38
|
+
onRemove: () => void;
|
|
39
|
+
}) => {
|
|
40
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className={cn(
|
|
45
|
+
"py-1 px-1.5 bg-muted rounded-md cursor-pointer flex flex-row gap-1 items-center text-xs",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
49
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
50
|
+
>
|
|
51
|
+
{isHovered ? (
|
|
52
|
+
<XIcon className="h-3 w-3 mt-0.5" onClick={onRemove} />
|
|
53
|
+
) : (
|
|
54
|
+
renderFileIcon(file)
|
|
55
|
+
)}
|
|
56
|
+
{file.name}
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function renderFileIcon(file: File): React.ReactNode {
|
|
62
|
+
const classNames = "h-3 w-3 mt-0.5";
|
|
63
|
+
|
|
64
|
+
if (file.type.startsWith("image/")) {
|
|
65
|
+
return <ImageIcon className={classNames} />;
|
|
66
|
+
} else if (file.type.startsWith("text/")) {
|
|
67
|
+
return <FileTextIcon className={classNames} />;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return <FileIcon className={classNames} />;
|
|
71
|
+
}
|