@marimo-team/islands 0.23.17-dev33 → 0.23.17-dev34
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-BEb8RZam.js → ConnectedDataExplorerComponent-emE_tsrt.js} +11 -11
- package/dist/{ErrorBoundary-8ZHte0h9.js → ErrorBoundary-o6wEnSCc.js} +2 -2
- package/dist/{any-language-editor-CioNaP-T.js → any-language-editor-Cls8lx9d.js} +4 -4
- package/dist/chat-ui-1-XgwfDv.js +8620 -0
- package/dist/{check-CCvJTB2E.js → check-D-BaEjEh.js} +1 -1
- package/dist/{common-C2Coc2Bh.js → common-Czr-rNF3.js} +18 -18
- package/dist/{copy-D45ZUY3j.js → copy-BkA3t7rJ.js} +1 -1
- package/dist/{dist-DgzzIPS4.js → dist-DarwE0ZO.js} +10 -10
- package/dist/{error-banner-DHfPl1w3.js → error-banner-dmkbm21G.js} +1 -1
- package/dist/{esm-DJsE6wi-.js → esm-CfyRntAe.js} +1 -1
- package/dist/{extends-xQ_2Cylt.js → extends-DzpiytFg.js} +3 -3
- package/dist/{formats-D10vOEsT.js → formats-DcO6TofP.js} +1 -1
- package/dist/{glide-data-editor-FiwJJzrV.js → glide-data-editor-C99Bp4Hk.js} +8 -8
- package/dist/{html-to-image-Bgb6aaXi.js → html-to-image-CgznkzKG.js} +13 -13
- package/dist/{input-DtTqU0xh.js → input-CwPuFkJt.js} +5 -5
- package/dist/{label-5IC840tS.js → label-CzHncDvS.js} +1 -1
- package/dist/main.js +29 -29
- package/dist/{mermaid-BNhC-PrB.js → mermaid-Sy0p3RJY.js} +4 -4
- package/dist/{process-output-D2Oxf11p.js → process-output-DUNqOGZS.js} +3 -3
- package/dist/{reveal-component--Ln8gdbm.js → reveal-component-CZ2AHfVt.js} +9 -9
- package/dist/{spec-C7rMyZ_8.js → spec-Be9MhFWh.js} +4 -4
- package/dist/{strings-CITcoFNY.js → strings-BUDhKc0s.js} +298 -298
- package/dist/{toDate-kxcuDOWd.js → toDate-eW__SzBR.js} +2 -2
- package/dist/{tooltip-BkSv4x8S.js → tooltip-CkCWlUOf.js} +2 -2
- package/dist/{types-kNUyqkDD.js → types-uENjXsiC.js} +1 -1
- package/dist/{useAsyncData-Uo4VFafD.js → useAsyncData-B8UxSH4O.js} +1 -1
- package/dist/{useDateFormatter-ZCfHadYP.js → useDateFormatter-BeN3fHMH.js} +2 -2
- package/dist/{useDeepCompareMemoize-s3ZvmdK3.js → useDeepCompareMemoize-BnhKck7C.js} +1 -1
- package/dist/{useLifecycle-CLPBATCv.js → useLifecycle-B7J7yvFt.js} +2 -2
- package/dist/{useTheme-CEMsu1Js.js → useTheme-BUY7pfI9.js} +1 -1
- package/dist/{vega-component-DGkcoXu3.js → vega-component-BOeMnvAb.js} +8 -8
- package/dist/{zod-DdNi9290.js → zod-yTHHzJiv.js} +36 -37
- package/package.json +3 -3
- package/src/components/chat/__tests__/chat-abort.test.ts +51 -0
- package/src/components/chat/chat-abort.ts +44 -0
- package/src/components/chat/chat-display.tsx +17 -0
- package/src/components/chat/chat-panel.tsx +85 -37
- package/src/components/chat/chat-utils.ts +1 -1
- package/src/components/editor/ai/add-cell-with-ai.tsx +2 -2
- package/src/components/editor/ai/ai-completion-editor.tsx +2 -2
- package/src/core/ai/constants.ts +7 -0
- package/src/core/ai/staged-cells.ts +3 -0
- package/src/plugins/impl/chat/chat-ui.tsx +51 -3
- package/dist/chat-ui-Cjd9kIcO.js +0 -8369
|
@@ -4,8 +4,8 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
|
|
|
4
4
|
import { t as __commonJSMin } from "./chunk-BNovOVIE.js";
|
|
5
5
|
import { g as Logger } from "./button-8aS1QcHr.js";
|
|
6
6
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
7
|
-
import { u as createLucideIcon } from "./dist-
|
|
8
|
-
import { S as useAtomValue, T as atom, a as KnownQueryParams, g as store, v as waitFor, y as isIslands } from "./useTheme-
|
|
7
|
+
import { u as createLucideIcon } from "./dist-DarwE0ZO.js";
|
|
8
|
+
import { S as useAtomValue, T as atom, a as KnownQueryParams, g as store, v as waitFor, y as isIslands } from "./useTheme-BUY7pfI9.js";
|
|
9
9
|
import { t as invariant } from "./invariant-wRzNXIsJ.js";
|
|
10
10
|
var CircleQuestionMark = createLucideIcon("circle-question-mark", [
|
|
11
11
|
["circle", {
|
|
@@ -2,9 +2,9 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
|
2
2
|
import { d as createSlottable, h as cn, p as useComposedRefs } from "./button-8aS1QcHr.js";
|
|
3
3
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
4
4
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
5
|
-
import { a as createPopperScope, i as Root2, n as Arrow, r as Content, s as Root, t as Anchor } from "./dist-
|
|
5
|
+
import { a as createPopperScope, i as Root2, n as Arrow, r as Content, s as Root, t as Anchor } from "./dist-DarwE0ZO.js";
|
|
6
6
|
import { t as require_jsx_runtime } from "./jsx-runtime-DebpN0FN.js";
|
|
7
|
-
import {
|
|
7
|
+
import { $ as StyleNamespace, X as withFullScreenAsRoot, Z as withSmartCollisionBoundary, _t as Primitive, dt as Presence, ft as useControllableState, gt as createContextScope, it as Portal, mt as composeEventHandlers, st as DismissableLayer, ut as useId } from "./zod-yTHHzJiv.js";
|
|
8
8
|
var import_react = /* @__PURE__ */ __toESM(require_react(), 1), import_jsx_runtime = /* @__PURE__ */ __toESM(require_jsx_runtime(), 1), [createTooltipContext, createTooltipScope] = createContextScope("Tooltip", [createPopperScope]), usePopperScope = createPopperScope(), PROVIDER_NAME = "TooltipProvider", DEFAULT_DELAY_DURATION = 700, TOOLTIP_OPEN = "tooltip.open", [TooltipProviderContextProvider, useTooltipProviderContext] = createTooltipContext(PROVIDER_NAME), TooltipProvider$1 = (n) => {
|
|
9
9
|
let { __scopeTooltip: j, delayDuration: M = DEFAULT_DELAY_DURATION, skipDelayDuration: N = 300, disableHoverableContent: P = false, children: F } = n, I = import_react.useRef(true), L = import_react.useRef(false), R = import_react.useRef(0);
|
|
10
10
|
return import_react.useEffect(() => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { u as createLucideIcon } from "./dist-
|
|
1
|
+
import { u as createLucideIcon } from "./dist-DarwE0ZO.js";
|
|
2
2
|
var Pencil = createLucideIcon("pencil", [["path", {
|
|
3
3
|
d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",
|
|
4
4
|
key: "1a8usu"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
2
2
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
3
3
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
4
|
-
import { A as useEvent_default } from "./useTheme-
|
|
4
|
+
import { A as useEvent_default } from "./useTheme-BUY7pfI9.js";
|
|
5
5
|
import { t as invariant } from "./invariant-wRzNXIsJ.js";
|
|
6
6
|
var import_compiler_runtime = require_compiler_runtime(), import_react = /* @__PURE__ */ __toESM(require_react(), 1), Result = {
|
|
7
7
|
error(e, s) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
2
2
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
3
|
-
import { u as createLucideIcon } from "./dist-
|
|
4
|
-
import { E as $18f2051aff69b9bf$export$43bb16f9c6d9e3f7 } from "./strings-
|
|
3
|
+
import { u as createLucideIcon } from "./dist-DarwE0ZO.js";
|
|
4
|
+
import { E as $18f2051aff69b9bf$export$43bb16f9c6d9e3f7 } from "./strings-BUDhKc0s.js";
|
|
5
5
|
var ChartPie = createLucideIcon("chart-pie", [["path", {
|
|
6
6
|
d: "M21 12c.552 0 1.005-.449.95-.998a10 10 0 0 0-8.953-8.951c-.55-.055-.998.398-.998.95v8a1 1 0 0 0 1 1z",
|
|
7
7
|
key: "pzmjnu"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
2
2
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
3
|
-
import { k as dequal } from "./useTheme-
|
|
3
|
+
import { k as dequal } from "./useTheme-BUY7pfI9.js";
|
|
4
4
|
var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
|
|
5
5
|
function useDeepCompareMemoize(e) {
|
|
6
6
|
let i = import_react.useRef(e);
|
|
@@ -2,9 +2,9 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
|
2
2
|
import { g as Logger, h as cn, r as cva } from "./button-8aS1QcHr.js";
|
|
3
3
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
4
4
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
5
|
-
import { u as createLucideIcon } from "./dist-
|
|
5
|
+
import { u as createLucideIcon } from "./dist-DarwE0ZO.js";
|
|
6
6
|
import { t as require_jsx_runtime } from "./jsx-runtime-DebpN0FN.js";
|
|
7
|
-
import { C as useSetAtom, T as atom } from "./useTheme-
|
|
7
|
+
import { C as useSetAtom, T as atom } from "./useTheme-BUY7pfI9.js";
|
|
8
8
|
var Calendar = createLucideIcon("calendar", [
|
|
9
9
|
["path", {
|
|
10
10
|
d: "M8 2v4",
|
|
@@ -2,7 +2,7 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
|
2
2
|
import { a as OverridingHotkeyProvider, g as Logger, s as resolvePlatform } from "./button-8aS1QcHr.js";
|
|
3
3
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
4
4
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
5
|
-
import { A as looseObject,
|
|
5
|
+
import { A as looseObject, I as record, L as string, N as number, P as object, T as boolean, b as _enum, w as array, z as union } from "./zod-yTHHzJiv.js";
|
|
6
6
|
import { t as merge_default } from "./merge-Be1CqGnU.js";
|
|
7
7
|
var import_react = /* @__PURE__ */ __toESM(require_react()), useInsertionEffect = typeof window < "u" ? import_react.useInsertionEffect || import_react.useLayoutEffect : () => {
|
|
8
8
|
};
|
|
@@ -2,24 +2,24 @@ import { s as __toESM } from "./chunk-BNovOVIE.js";
|
|
|
2
2
|
import { g as Logger, h as cn, m as Events } from "./button-8aS1QcHr.js";
|
|
3
3
|
import { t as require_react } from "./react-DA-nE2FX.js";
|
|
4
4
|
import { t as require_compiler_runtime } from "./compiler-runtime-CEbnTgxf.js";
|
|
5
|
-
import { c as asRemoteURL, w as CircleQuestionMark } from "./toDate-
|
|
5
|
+
import { c as asRemoteURL, w as CircleQuestionMark } from "./toDate-eW__SzBR.js";
|
|
6
6
|
import "./react-dom-BTJzcVJ9.js";
|
|
7
7
|
import { t as require_jsx_runtime } from "./jsx-runtime-DebpN0FN.js";
|
|
8
|
-
import "./zod-
|
|
9
|
-
import { n as ErrorBanner } from "./error-banner-
|
|
10
|
-
import { t as Tooltip } from "./tooltip-
|
|
8
|
+
import "./zod-yTHHzJiv.js";
|
|
9
|
+
import { n as ErrorBanner } from "./error-banner-dmkbm21G.js";
|
|
10
|
+
import { t as Tooltip } from "./tooltip-CkCWlUOf.js";
|
|
11
11
|
import { t as debounce_default } from "./debounce-BOD3DbfP.js";
|
|
12
|
-
import { A as useEvent_default, n as useTheme } from "./useTheme-
|
|
12
|
+
import { A as useEvent_default, n as useTheme } from "./useTheme-BUY7pfI9.js";
|
|
13
13
|
import { s as uniq } from "./arrays-sEtDRoG4.js";
|
|
14
14
|
import { t as Objects } from "./objects-De59twF-.js";
|
|
15
|
-
import { a as isValid, i as AlertTitle, n as Alert, t as arrow } from "./formats-
|
|
15
|
+
import { a as isValid, i as AlertTitle, n as Alert, t as arrow } from "./formats-DcO6TofP.js";
|
|
16
16
|
import { V as getContainerWidth, n as formats } from "./vega-loader.browser-DmpxCJqx.js";
|
|
17
17
|
import { a as tooltipHandler, n as vegaLoadData } from "./loader-DP2JkPth.js";
|
|
18
18
|
import { t as j } from "./react-vega-BazDALhI.js";
|
|
19
19
|
import "./defaultLocale-BpsHxBd7.js";
|
|
20
20
|
import "./defaultLocale-DoeErsX2.js";
|
|
21
|
-
import { t as useAsyncData } from "./useAsyncData-
|
|
22
|
-
import { t as useDeepCompareMemoize } from "./useDeepCompareMemoize-
|
|
21
|
+
import { t as useAsyncData } from "./useAsyncData-B8UxSH4O.js";
|
|
22
|
+
import { t as useDeepCompareMemoize } from "./useDeepCompareMemoize-BnhKck7C.js";
|
|
23
23
|
import { t as Semaphore } from "./semaphore-C8rmHdhj.js";
|
|
24
24
|
import { t as useVegaContainerRemeasure } from "./use-vega-container-remeasure-DGNrIQSk.js";
|
|
25
25
|
var import_compiler_runtime = require_compiler_runtime(), import_react = /* @__PURE__ */ __toESM(require_react(), 1);
|
|
@@ -11758,77 +11758,76 @@ var external_exports = /* @__PURE__ */ __export({
|
|
|
11758
11758
|
config(en_default());
|
|
11759
11759
|
var zod_default = external_exports;
|
|
11760
11760
|
export {
|
|
11761
|
-
|
|
11761
|
+
StyleNamespace as $,
|
|
11762
11762
|
looseObject as A,
|
|
11763
|
-
|
|
11763
|
+
unknown as B,
|
|
11764
11764
|
any as C,
|
|
11765
11765
|
discriminatedUnion as D,
|
|
11766
11766
|
custom as E,
|
|
11767
11767
|
optional as F,
|
|
11768
|
-
|
|
11769
|
-
|
|
11768
|
+
parseAsync$1 as G,
|
|
11769
|
+
ZodError as H,
|
|
11770
11770
|
record as I,
|
|
11771
|
-
|
|
11772
|
-
|
|
11773
|
-
|
|
11771
|
+
MAX_HEIGHT_OFFSET as J,
|
|
11772
|
+
$ZodError as K,
|
|
11773
|
+
string$1 as L,
|
|
11774
11774
|
never as M,
|
|
11775
11775
|
number$1 as N,
|
|
11776
11776
|
lazy as O,
|
|
11777
11777
|
object as P,
|
|
11778
|
-
|
|
11779
|
-
|
|
11778
|
+
isInVscodeExtension as Q,
|
|
11779
|
+
tuple as R,
|
|
11780
11780
|
_null as S,
|
|
11781
11781
|
boolean$1 as T,
|
|
11782
|
-
|
|
11783
|
-
|
|
11784
|
-
|
|
11785
|
-
|
|
11786
|
-
|
|
11787
|
-
|
|
11782
|
+
toJSONSchema as U,
|
|
11783
|
+
safeParseAsync as V,
|
|
11784
|
+
parse$1 as W,
|
|
11785
|
+
withFullScreenAsRoot as X,
|
|
11786
|
+
useFullScreenElement as Y,
|
|
11787
|
+
withSmartCollisionBoundary as Z,
|
|
11788
11788
|
ZodString as _,
|
|
11789
|
-
|
|
11789
|
+
Primitive as _t,
|
|
11790
11790
|
ZodIssueCode as a,
|
|
11791
|
-
|
|
11791
|
+
FocusScope as at,
|
|
11792
11792
|
_enum as b,
|
|
11793
11793
|
ZodBoolean as c,
|
|
11794
|
-
|
|
11794
|
+
Root as ct,
|
|
11795
11795
|
ZodDiscriminatedUnion as d,
|
|
11796
|
-
|
|
11797
|
-
|
|
11796
|
+
Presence as dt,
|
|
11797
|
+
hideOthers as et,
|
|
11798
11798
|
ZodEnum as f,
|
|
11799
|
-
|
|
11799
|
+
useControllableState as ft,
|
|
11800
11800
|
ZodOptional as g,
|
|
11801
|
-
|
|
11801
|
+
createContextScope as gt,
|
|
11802
11802
|
ZodObject as h,
|
|
11803
|
-
|
|
11803
|
+
createContext2 as ht,
|
|
11804
11804
|
string as i,
|
|
11805
|
-
|
|
11805
|
+
Portal as it,
|
|
11806
11806
|
nan as j,
|
|
11807
11807
|
literal as k,
|
|
11808
11808
|
ZodDate as l,
|
|
11809
|
-
|
|
11809
|
+
useCallbackRef$1 as lt,
|
|
11810
11810
|
ZodNumber as m,
|
|
11811
|
-
|
|
11811
|
+
composeEventHandlers as mt,
|
|
11812
11812
|
date as n,
|
|
11813
|
-
|
|
11813
|
+
__awaiter as nt,
|
|
11814
11814
|
ZodAny as o,
|
|
11815
|
-
|
|
11815
|
+
Branch as ot,
|
|
11816
11816
|
ZodLiteral as p,
|
|
11817
|
-
|
|
11818
|
-
|
|
11817
|
+
useLayoutEffect2 as pt,
|
|
11818
|
+
prettifyError as q,
|
|
11819
11819
|
number as r,
|
|
11820
|
-
|
|
11820
|
+
useFocusGuards as rt,
|
|
11821
11821
|
ZodArray as s,
|
|
11822
|
-
|
|
11822
|
+
DismissableLayer as st,
|
|
11823
11823
|
zod_default as t,
|
|
11824
|
-
|
|
11824
|
+
Combination_default as tt,
|
|
11825
11825
|
ZodDefault as u,
|
|
11826
|
-
|
|
11826
|
+
useId as ut,
|
|
11827
11827
|
ZodType as v,
|
|
11828
|
-
|
|
11828
|
+
dispatchDiscreteCustomEvent as vt,
|
|
11829
11829
|
array as w,
|
|
11830
11830
|
_instanceof as x,
|
|
11831
11831
|
ZodUnion as y,
|
|
11832
|
-
|
|
11833
|
-
tuple as z
|
|
11832
|
+
union as z
|
|
11834
11833
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marimo-team/islands",
|
|
3
|
-
"version": "0.23.17-
|
|
3
|
+
"version": "0.23.17-dev34",
|
|
4
4
|
"main": "dist/main.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"./unstable_internal/*": "./src/*"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@ai-sdk/react": "^
|
|
24
|
+
"@ai-sdk/react": "^4.0.40",
|
|
25
25
|
"@anywidget/types": "^0.4.0",
|
|
26
26
|
"@codemirror/autocomplete": "^6.20.1",
|
|
27
27
|
"@codemirror/commands": "^6.10.2",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"@xterm/addon-web-links": "^0.12.0",
|
|
93
93
|
"@xterm/xterm": "^5.5.0",
|
|
94
94
|
"@zed-industries/agent-client-protocol": "^0.4.5",
|
|
95
|
-
"ai": "^
|
|
95
|
+
"ai": "^7.0.37",
|
|
96
96
|
"ansi_up": "^6.0.6",
|
|
97
97
|
"class-variance-authority": "^0.7.1",
|
|
98
98
|
"clsx": "^2.1.1",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { UIMessageChunk } from "ai";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
describeChatAbortReason,
|
|
7
|
+
getAbortReasonFromChunk,
|
|
8
|
+
resolveChatAbortReason,
|
|
9
|
+
USER_CANCELLED_ABORT_REASON,
|
|
10
|
+
} from "../chat-abort";
|
|
11
|
+
|
|
12
|
+
describe("chat-abort", () => {
|
|
13
|
+
it("reads reason from abort chunks", () => {
|
|
14
|
+
const chunk: UIMessageChunk = {
|
|
15
|
+
type: "abort",
|
|
16
|
+
reason: USER_CANCELLED_ABORT_REASON,
|
|
17
|
+
};
|
|
18
|
+
expect(getAbortReasonFromChunk(chunk)).toBe(USER_CANCELLED_ABORT_REASON);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns undefined for non-abort chunks", () => {
|
|
22
|
+
expect(
|
|
23
|
+
getAbortReasonFromChunk({ type: "text-delta", id: "1", delta: "hi" }),
|
|
24
|
+
).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("defaults client-side aborts to user_cancelled", () => {
|
|
28
|
+
expect(resolveChatAbortReason({ isAbort: true, streamReason: null })).toBe(
|
|
29
|
+
USER_CANCELLED_ABORT_REASON,
|
|
30
|
+
);
|
|
31
|
+
expect(resolveChatAbortReason({ isAbort: false })).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("preserves stream abort reasons when present", () => {
|
|
35
|
+
expect(
|
|
36
|
+
resolveChatAbortReason({ isAbort: true, streamReason: "timeout" }),
|
|
37
|
+
).toBe("timeout");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("describes known and unknown abort reasons", () => {
|
|
41
|
+
expect(describeChatAbortReason(USER_CANCELLED_ABORT_REASON)).toBe(
|
|
42
|
+
"Generation stopped",
|
|
43
|
+
);
|
|
44
|
+
expect(describeChatAbortReason("timeout")).toBe(
|
|
45
|
+
"Generation stopped: timed out",
|
|
46
|
+
);
|
|
47
|
+
expect(describeChatAbortReason("provider_reset")).toBe(
|
|
48
|
+
"Generation stopped: provider_reset",
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { UIMessageChunk } from "ai";
|
|
4
|
+
|
|
5
|
+
/** Matches pydantic-ai / marimo AbortChunk.reason for user Stop. */
|
|
6
|
+
export const USER_CANCELLED_ABORT_REASON = "user_cancelled";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Read an abort reason from a UI message stream chunk, if present.
|
|
10
|
+
*/
|
|
11
|
+
export function getAbortReasonFromChunk(
|
|
12
|
+
chunk: UIMessageChunk,
|
|
13
|
+
): string | undefined {
|
|
14
|
+
if (chunk.type !== "abort") {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
return chunk.reason;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the abort reason for logging/UI. Client-side Stop often never
|
|
22
|
+
* receives a server AbortChunk (the fetch is aborted first), so default to
|
|
23
|
+
* user_cancelled when the turn ended as an abort.
|
|
24
|
+
*/
|
|
25
|
+
export function resolveChatAbortReason(opts: {
|
|
26
|
+
isAbort: boolean;
|
|
27
|
+
streamReason?: string | null;
|
|
28
|
+
}): string | null {
|
|
29
|
+
if (!opts.isAbort) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return opts.streamReason || USER_CANCELLED_ABORT_REASON;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function describeChatAbortReason(reason: string): string {
|
|
36
|
+
switch (reason) {
|
|
37
|
+
case USER_CANCELLED_ABORT_REASON:
|
|
38
|
+
return "Generation stopped";
|
|
39
|
+
case "timeout":
|
|
40
|
+
return "Generation stopped: timed out";
|
|
41
|
+
default:
|
|
42
|
+
return `Generation stopped: ${reason}`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -92,6 +92,17 @@ export const renderUIMessage = ({
|
|
|
92
92
|
);
|
|
93
93
|
case "file":
|
|
94
94
|
return <AttachmentRenderer attachment={part} key={index} />;
|
|
95
|
+
case "reasoning-file":
|
|
96
|
+
return (
|
|
97
|
+
<AttachmentRenderer
|
|
98
|
+
key={index}
|
|
99
|
+
attachment={{
|
|
100
|
+
type: "file",
|
|
101
|
+
mediaType: part.mediaType,
|
|
102
|
+
url: part.url,
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
95
106
|
case "dynamic-tool":
|
|
96
107
|
return (
|
|
97
108
|
<ToolCallView
|
|
@@ -128,6 +139,12 @@ export const renderUIMessage = ({
|
|
|
128
139
|
href={part.url}
|
|
129
140
|
/>
|
|
130
141
|
);
|
|
142
|
+
case "custom":
|
|
143
|
+
return (
|
|
144
|
+
<div key={index} className="text-sm text-muted-foreground">
|
|
145
|
+
{part.kind}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
131
148
|
case "step-start":
|
|
132
149
|
return null;
|
|
133
150
|
default:
|
|
@@ -6,7 +6,6 @@ import { storePrompt } from "@marimo-team/codemirror-ai";
|
|
|
6
6
|
import type { ReactCodeMirrorRef } from "@uiw/react-codemirror";
|
|
7
7
|
import {
|
|
8
8
|
type ChatAddToolApproveResponseFunction,
|
|
9
|
-
DefaultChatTransport,
|
|
10
9
|
type FileUIPart,
|
|
11
10
|
safeValidateUIMessages,
|
|
12
11
|
type TextUIPart,
|
|
@@ -38,6 +37,7 @@ import {
|
|
|
38
37
|
} from "@/components/ui/select";
|
|
39
38
|
import { replaceMessagesInChat } from "@/core/ai/chat-utils";
|
|
40
39
|
import { useModelChange } from "@/core/ai/config";
|
|
40
|
+
import { AI_SDK_UI_THROTTLE_MS } from "@/core/ai/constants";
|
|
41
41
|
import { AiModelId } from "@/core/ai/ids/ids";
|
|
42
42
|
import { useStagedAICellsActions } from "@/core/ai/staged-cells";
|
|
43
43
|
import {
|
|
@@ -71,6 +71,7 @@ import {
|
|
|
71
71
|
isContextAttachment,
|
|
72
72
|
resolveChatContext,
|
|
73
73
|
} from "../editor/ai/completion-utils";
|
|
74
|
+
import { StreamingChunkTransport } from "../editor/ai/transport/chat-transport";
|
|
74
75
|
import { PanelEmptyState } from "../editor/chrome/panels/empty-state";
|
|
75
76
|
import { CopyClipboardIcon } from "../icons/copy-icon";
|
|
76
77
|
import { useImperativeModal } from "../modal/ImperativeModal";
|
|
@@ -83,6 +84,11 @@ import {
|
|
|
83
84
|
FileAttachmentPill,
|
|
84
85
|
SendButton,
|
|
85
86
|
} from "./chat-components";
|
|
87
|
+
import {
|
|
88
|
+
describeChatAbortReason,
|
|
89
|
+
getAbortReasonFromChunk,
|
|
90
|
+
resolveChatAbortReason,
|
|
91
|
+
} from "./chat-abort";
|
|
86
92
|
import { renderUIMessage } from "./chat-display";
|
|
87
93
|
import { ChatHistoryPopover } from "./chat-history-popover";
|
|
88
94
|
import {
|
|
@@ -533,6 +539,12 @@ const ChatPanelBody = () => {
|
|
|
533
539
|
const newMessageInputRef = useRef<ReactCodeMirrorRef>(null);
|
|
534
540
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
535
541
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
542
|
+
const lastAbortReasonRef = useRef<string | null>(null);
|
|
543
|
+
const [abortNotice, setAbortNotice] = useState<string | null>(null);
|
|
544
|
+
const clearAbortState = useEvent(() => {
|
|
545
|
+
lastAbortReasonRef.current = null;
|
|
546
|
+
setAbortNotice(null);
|
|
547
|
+
});
|
|
536
548
|
const runtimeManager = useRuntimeManager();
|
|
537
549
|
const { invokeAiTool, sendRun } = useRequestClient();
|
|
538
550
|
const { openModal, closeModal } = useImperativeModal();
|
|
@@ -562,45 +574,54 @@ const ChatPanelBody = () => {
|
|
|
562
574
|
id: chatId,
|
|
563
575
|
} = useChat({
|
|
564
576
|
id: activeChatId,
|
|
577
|
+
throttle: AI_SDK_UI_THROTTLE_MS,
|
|
565
578
|
sendAutomaticallyWhen: ({ messages }) => hasPendingToolCalls(messages),
|
|
566
579
|
messages: activeChat?.messages || [], // initial messages
|
|
567
|
-
transport: new
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
580
|
+
transport: new StreamingChunkTransport(
|
|
581
|
+
{
|
|
582
|
+
api: runtimeManager.getAiURL("chat").toString(),
|
|
583
|
+
headers: () => runtimeManager.headers(),
|
|
584
|
+
prepareSendMessagesRequest: async (options) => {
|
|
585
|
+
// Canary: flag outgoing messages that don't match the AI SDK's own
|
|
586
|
+
// schema. The server-side sanitizer in `_pydantic_ai_utils.py` corrects these before validation;
|
|
587
|
+
// this log surfaces drift early without affecting the request.
|
|
588
|
+
const validation = await safeValidateUIMessages({
|
|
589
|
+
messages: options.messages,
|
|
590
|
+
});
|
|
591
|
+
if (!validation.success) {
|
|
592
|
+
Logger.debug(
|
|
593
|
+
"Outgoing chat messages failed AI SDK schema validation",
|
|
594
|
+
validation.error,
|
|
595
|
+
);
|
|
596
|
+
}
|
|
583
597
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
598
|
+
const completionBody = {
|
|
599
|
+
uiMessages: options.messages,
|
|
600
|
+
includeOtherCode: getCodes(""),
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
// Call this here to ensure the value is not stale
|
|
604
|
+
const chatMode = store.get(aiAtom)?.mode || DEFAULT_MODE;
|
|
605
|
+
const tools = FRONTEND_TOOL_REGISTRY.getToolSchemas(chatMode);
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
api: runtimeManager.getAiURL("chat").toString(),
|
|
609
|
+
body: {
|
|
610
|
+
tools,
|
|
611
|
+
...options,
|
|
612
|
+
...completionBody,
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
(chunk) => {
|
|
618
|
+
const abortReason = getAbortReasonFromChunk(chunk);
|
|
619
|
+
if (abortReason !== undefined) {
|
|
620
|
+
lastAbortReasonRef.current = abortReason;
|
|
621
|
+
}
|
|
601
622
|
},
|
|
602
|
-
|
|
603
|
-
onFinish: ({ messages, isError, isAbort }) => {
|
|
623
|
+
),
|
|
624
|
+
onFinish: ({ messages, isError, isAbort, finishReason }) => {
|
|
604
625
|
setChatState((prev) => {
|
|
605
626
|
return replaceMessagesInChat({
|
|
606
627
|
chatState: prev,
|
|
@@ -608,6 +629,22 @@ const ChatPanelBody = () => {
|
|
|
608
629
|
messages: messages,
|
|
609
630
|
});
|
|
610
631
|
});
|
|
632
|
+
|
|
633
|
+
if (isAbort) {
|
|
634
|
+
const reason = resolveChatAbortReason({
|
|
635
|
+
isAbort: true,
|
|
636
|
+
streamReason: lastAbortReasonRef.current,
|
|
637
|
+
});
|
|
638
|
+
clearAbortState();
|
|
639
|
+
if (reason != null) {
|
|
640
|
+
const description = describeChatAbortReason(reason);
|
|
641
|
+
Logger.debug("Chat stream aborted", { reason, finishReason });
|
|
642
|
+
setAbortNotice(description);
|
|
643
|
+
}
|
|
644
|
+
} else {
|
|
645
|
+
clearAbortState();
|
|
646
|
+
}
|
|
647
|
+
|
|
611
648
|
tryFlushQueuedMessages(messages, { isError, isAbort });
|
|
612
649
|
},
|
|
613
650
|
onToolCall: async ({ toolCall }) => {
|
|
@@ -628,6 +665,7 @@ const ChatPanelBody = () => {
|
|
|
628
665
|
});
|
|
629
666
|
|
|
630
667
|
const sendUserMessage = useEvent((parts: ChatMessagePart[]) => {
|
|
668
|
+
clearAbortState();
|
|
631
669
|
sendMessage({ role: "user", parts });
|
|
632
670
|
});
|
|
633
671
|
|
|
@@ -701,7 +739,8 @@ const ChatPanelBody = () => {
|
|
|
701
739
|
useEffect(() => {
|
|
702
740
|
setIsScrolledToBottom(true);
|
|
703
741
|
clearQueuedMessages();
|
|
704
|
-
|
|
742
|
+
clearAbortState();
|
|
743
|
+
}, [activeChatId, clearQueuedMessages, clearAbortState]);
|
|
705
744
|
|
|
706
745
|
useEffect(() => {
|
|
707
746
|
if (!isScrolledToBottom) {
|
|
@@ -833,6 +872,7 @@ const ChatPanelBody = () => {
|
|
|
833
872
|
);
|
|
834
873
|
|
|
835
874
|
const handleReload = () => {
|
|
875
|
+
clearAbortState();
|
|
836
876
|
regenerate();
|
|
837
877
|
};
|
|
838
878
|
|
|
@@ -987,6 +1027,14 @@ const ChatPanelBody = () => {
|
|
|
987
1027
|
</Button>
|
|
988
1028
|
</div>
|
|
989
1029
|
)}
|
|
1030
|
+
|
|
1031
|
+
{abortNotice && !isLoading && !error && (
|
|
1032
|
+
<div className="flex justify-center py-2">
|
|
1033
|
+
<span className="text-xs text-muted-foreground">
|
|
1034
|
+
{abortNotice}
|
|
1035
|
+
</span>
|
|
1036
|
+
</div>
|
|
1037
|
+
)}
|
|
990
1038
|
</div>
|
|
991
1039
|
|
|
992
1040
|
<ScrollToBottomButton
|
|
@@ -291,7 +291,7 @@ export function shouldFlushQueue(opts: {
|
|
|
291
291
|
export function useMessageQueue() {
|
|
292
292
|
const [messages, setMessages] = useState<QueuedUserMessage[]>([]);
|
|
293
293
|
// Mirror the queue in a ref so `flushNext` reads the latest value even when
|
|
294
|
-
// invoked from a callback (e.g. `onFinish`)
|
|
294
|
+
// invoked from a callback (e.g. `onFinish`) in the same tick as `enqueue`.
|
|
295
295
|
const messagesRef = useRef<QueuedUserMessage[]>([]);
|
|
296
296
|
const hasQueuedRef = useRef(false);
|
|
297
297
|
messagesRef.current = messages;
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
} from "@/components/ui/dropdown-menu";
|
|
52
52
|
import { toast } from "@/components/ui/use-toast";
|
|
53
53
|
import { AiModelId } from "@/core/ai/ids/ids";
|
|
54
|
+
import { AI_SDK_UI_THROTTLE_MS } from "@/core/ai/constants";
|
|
54
55
|
import { stagedAICellsAtom, useStagedCells } from "@/core/ai/staged-cells";
|
|
55
56
|
import type { ToolNotebookContext } from "@/core/ai/tools/base";
|
|
56
57
|
import { useCellActions } from "@/core/cells/cells";
|
|
@@ -117,8 +118,7 @@ export const AddCellWithAI: React.FC<{
|
|
|
117
118
|
};
|
|
118
119
|
|
|
119
120
|
const { sendMessage, stop, status, addToolOutput } = useChat({
|
|
120
|
-
|
|
121
|
-
experimental_throttle: 100,
|
|
121
|
+
throttle: AI_SDK_UI_THROTTLE_MS,
|
|
122
122
|
transport: new StreamingChunkTransport(
|
|
123
123
|
{
|
|
124
124
|
api: runtimeManager.getAiURL("completion").toString(),
|
|
@@ -27,6 +27,7 @@ import { Label } from "@/components/ui/label";
|
|
|
27
27
|
import { Switch } from "@/components/ui/switch";
|
|
28
28
|
import { Tooltip } from "@/components/ui/tooltip";
|
|
29
29
|
import { toast } from "@/components/ui/use-toast";
|
|
30
|
+
import { AI_SDK_UI_THROTTLE_MS } from "@/core/ai/constants";
|
|
30
31
|
import { stripWrappingBackticks } from "@/core/ai/strip-wrapping-backticks";
|
|
31
32
|
import { type AiCompletionCell, includeOtherCellsAtom } from "@/core/ai/state";
|
|
32
33
|
import type { CellId } from "@/core/cells/ids";
|
|
@@ -124,8 +125,7 @@ export const AiCompletionEditor: React.FC<Props> = ({
|
|
|
124
125
|
api: runtimeManager.getAiURL("completion").toString(),
|
|
125
126
|
headers: runtimeManager.headers(),
|
|
126
127
|
initialInput: initialPrompt,
|
|
127
|
-
|
|
128
|
-
experimental_throttle: 100,
|
|
128
|
+
throttle: AI_SDK_UI_THROTTLE_MS,
|
|
129
129
|
body: {
|
|
130
130
|
...(Object.keys(completionBody).length > 0
|
|
131
131
|
? completionBody
|