Skip to content

Commit b420911

Browse files
Merge pull request #725 from Kanaries/release-0.5.0.0
Release 0.5.0.0
2 parents a60a2e9 + b107b1b commit b420911

File tree

14 files changed

+2742
-3920
lines changed

14 files changed

+2742
-3920
lines changed

.github/workflows/auto-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717

1818
strategy:
1919
matrix:
20-
node-version: [16.x]
20+
node-version: [22.x]
2121
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
2222

2323
steps:

.github/workflows/publish.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010

1111
strategy:
1212
matrix:
13-
node-version: [16.x, 18.x]
13+
node-version: [22.x]
1414
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
1515

1616
steps:
@@ -29,7 +29,7 @@ jobs:
2929
chmod u+x ./scripts/compile.sh
3030
./scripts/compile.sh
3131
- name: Uploading dist
32-
if: ${{ matrix.node-version == '16.x' }}
32+
if: ${{ matrix.node-version == '22.x' }}
3333
uses: actions/upload-artifact@v4
3434
with:
3535
name: pygwalker-app

app/package.json

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,17 @@
1818
"@anywidget/react": "^0.0.8",
1919
"@headlessui/react": "^1.7.14",
2020
"@heroicons/react": "^2.0.8",
21-
"@kanaries/graphic-walker": "0.4.70",
21+
"@kanaries/graphic-walker": "0.5.0-alpha.2",
2222
"@kanaries/gw-dsl-parser": "0.1.49",
23-
"@radix-ui/react-checkbox": "^1.0.4",
24-
"@radix-ui/react-dialog": "^1.0.5",
25-
"@radix-ui/react-icons": "^1.3.0",
26-
"@radix-ui/react-label": "^2.0.2",
27-
"@radix-ui/react-select": "^2.0.0",
28-
"@radix-ui/react-slot": "^1.0.2",
29-
"@radix-ui/react-tabs": "^1.0.4",
30-
"@radix-ui/react-toggle": "^1.0.3",
31-
"@radix-ui/react-toggle-group": "^1.0.4",
23+
"@radix-ui/react-checkbox": "^1.3.3",
24+
"@radix-ui/react-dialog": "^1.1.15",
25+
"@radix-ui/react-icons": "^1.3.2",
26+
"@radix-ui/react-label": "^2.1.8",
27+
"@radix-ui/react-select": "^2.2.6",
28+
"@radix-ui/react-slot": "^1.2.4",
29+
"@radix-ui/react-tabs": "^1.1.13",
30+
"@radix-ui/react-toggle": "^1.1.10",
31+
"@radix-ui/react-toggle-group": "^1.1.11",
3232
"@segment/analytics-next": "^1.69.0",
3333
"autoprefixer": "^10.3.5",
3434
"buffer": "^6.0.3",
@@ -39,24 +39,24 @@
3939
"mobx": "^6.9.0",
4040
"mobx-react-lite": "^3.4.3",
4141
"postcss": "^8.3.7",
42-
"react": "^18.x",
43-
"react-dom": "^18.x",
42+
"react": "19.x",
43+
"react-dom": "19.x",
4444
"react-syntax-highlighter": "^15.5.0",
45+
"streamlit-component-lib": "^2.0.0",
4546
"styled-components": "^5.3.6",
4647
"tailwind-merge": "^1.14.0",
4748
"tailwindcss": "^3.2.4",
4849
"tailwindcss-animate": "^1.0.7",
49-
"uuid": "^8.3.2",
50-
"streamlit-component-lib": "^2.0.0"
50+
"uuid": "^8.3.2"
5151
},
5252
"devDependencies": {
5353
"@rollup/plugin-commonjs": "^24.0.x",
5454
"@rollup/plugin-replace": "^5.0.x",
5555
"@rollup/plugin-terser": "^0.4.x",
5656
"@rollup/plugin-typescript": "^11.0.x",
5757
"@types/node": "^20.7.2",
58-
"@types/react": "^18.x",
59-
"@types/react-dom": "^18.x",
58+
"@types/react": "^19.x",
59+
"@types/react-dom": "^19.x",
6060
"@types/react-syntax-highlighter": "^15.5.7",
6161
"@types/styled-components": "^5.1.26",
6262
"@vitejs/plugin-react": "^3.1.x",
@@ -65,10 +65,10 @@
6565
"vite-plugin-wasm": "^3.2.2"
6666
},
6767
"resolutions": {
68-
"react": "^18.x",
69-
"react-dom": "^18.x",
70-
"@types/react": "^18.x",
71-
"@types/react-dom": "^18.x"
68+
"react": "^19.x",
69+
"react-dom": "^19.x",
70+
"@types/react": "^19.x",
71+
"@types/react-dom": "^19.x"
7272
},
7373
"peerDependencies": {},
7474
"prettier": {

app/src/components/initModal/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from "react";
22
import { observer } from "mobx-react-lite";
3-
import { Dialog, DialogContent } from "@/components/ui/dialog";
3+
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
44

55
import commonStore from "../../store/common";
66

@@ -10,6 +10,7 @@ const InitModal: React.FC<IInitModal> = observer((props) => {
1010
return (
1111
<Dialog open={commonStore.initModalOpen} modal={true}>
1212
<DialogContent hideClose>
13+
<DialogTitle className="sr-only">{commonStore.initModalInfo.title}</DialogTitle>
1314
<div className="flex justify-between mb-1">
1415
<span className="text-base font-medium text-blue-700 dark:text-white">{commonStore.initModalInfo.title}</span>
1516
<span className="text-sm font-medium text-blue-700 dark:text-white">

app/src/components/preview/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
88
import style from '@/index.css?inline'
99

1010
interface IPreviewProps {
11+
gid: string;
1112
themeKey: string;
1213
dark: IDarkMode;
1314
charts: {
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import React, { useState } from "react";
2+
import { tracker } from "@/utils/tracker";
3+
4+
const RUNCELL_LOGO_URL = "https://www.runcell.dev/runcell-logo.svg";
5+
const RUNCELL_WEBSITE = "https://www.runcell.dev?utm_source=pygwalker";
6+
7+
const LLM_LOGOS = [
8+
"https://www.runcell.dev/llm-icons/openai.svg",
9+
"https://www.runcell.dev/llm-icons/claude-color.svg",
10+
"https://www.runcell.dev/llm-icons/gemini-color.svg",
11+
"https://www.runcell.dev/llm-icons/deepseek-color.svg",
12+
];
13+
14+
interface RuncellBannerProps {
15+
env?: string;
16+
}
17+
18+
const checkRuncellInstalled = (): boolean => {
19+
try {
20+
// Runcell JupyterLab plugin stores user status in localStorage
21+
const runcellUser = window.parent.localStorage.getItem("plugin_auth_user_v2");
22+
return runcellUser !== null;
23+
} catch {
24+
return false;
25+
}
26+
};
27+
28+
export const RuncellBanner: React.FC<RuncellBannerProps> = ({ env }) => {
29+
const [dismissed, setDismissed] = useState(false);
30+
31+
// Only show in Jupyter environments
32+
if (env !== "jupyter_widgets" && env !== "anywidget") {
33+
return null;
34+
}
35+
36+
// Don't show if runcell is installed or user dismissed
37+
if (checkRuncellInstalled() || dismissed) {
38+
return null;
39+
}
40+
41+
const handleClick = () => {
42+
tracker.track("click", { entity: "runcell_banner" });
43+
window.open(RUNCELL_WEBSITE, "_blank");
44+
};
45+
46+
const handleDismiss = (e: React.MouseEvent) => {
47+
e.stopPropagation();
48+
tracker.track("click", { entity: "runcell_banner_dismiss" });
49+
setDismissed(true);
50+
};
51+
52+
return (
53+
<div
54+
className="flex items-center justify-between gap-3 px-4 py-2 mb-2 rounded-md bg-gradient-to-r from-purple-50 to-blue-50 dark:from-purple-950/30 dark:to-blue-950/30 border border-purple-200 dark:border-purple-800 cursor-pointer hover:shadow-md transition-shadow"
55+
onClick={handleClick}
56+
>
57+
<div className="flex items-center gap-3">
58+
<img
59+
src={RUNCELL_LOGO_URL}
60+
alt="Runcell"
61+
className="w-6 h-6"
62+
/>
63+
<span className="text-sm text-gray-700 dark:text-gray-300">
64+
Enable AI Agent for data analysis with pip install runcell
65+
</span>
66+
</div>
67+
<div className="flex items-center gap-2">
68+
<div className="flex items-center gap-1 px-2 py-1 rounded-full bg-white/80">
69+
{LLM_LOGOS.map((logo, index) => (
70+
<img
71+
key={index}
72+
src={logo}
73+
alt="LLM"
74+
className="w-4 h-4"
75+
/>
76+
))}
77+
</div>
78+
<button
79+
onClick={handleDismiss}
80+
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 text-lg leading-none px-1"
81+
aria-label="Dismiss"
82+
>
83+
×
84+
</button>
85+
</div>
86+
</div>
87+
);
88+
};
89+
90+
export default RuncellBanner;

app/src/index.tsx

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useCallback, useContext, useEffect, useState } from 'react';
2-
import ReactDOM from 'react-dom';
2+
import { createRoot } from 'react-dom/client';
33
import { observer } from "mobx-react-lite";
44
import { reaction } from "mobx"
55
import { GraphicWalker, PureRenderer, GraphicRenderer, TableWalker } from '@kanaries/graphic-walker'
@@ -27,6 +27,7 @@ import InitModal from './components/initModal';
2727
import { getSaveTool } from './tools/saveTool';
2828
import { getExportTool } from './tools/exportTool';
2929
import { getExportDataframeTool } from './tools/exportDataframe';
30+
import { getRuncellTool } from './tools/runcellTool';
3031
import { formatExportedChartDatas } from "./utils/save";
3132
import { tracker } from "@/utils/tracker";
3233
import Notification from "./notify"
@@ -50,6 +51,7 @@ import { currentMediaTheme } from './utils/theme';
5051
import { AppContext, darkModeContext } from './store/context';
5152
import FormatSpec from './utils/formatSpec';
5253
import { getOpenDesktopTool } from './tools/openDesktop';
54+
import RuncellBanner from './components/runcellBanner';
5355

5456

5557
const initChart = async (gwRef: React.MutableRefObject<IGWHandler | null>, total: number, props: IAppProps) => {
@@ -208,10 +210,11 @@ const ExploreApp: React.FC<IAppProps & {initChartFlag: boolean}> = (props) => {
208210
}, 0);
209211
}, [mode]);
210212

213+
const runcellTool = getRuncellTool();
211214
const exportTool = getExportTool(setExportOpen);
212215
const openInDesktopTool = getOpenDesktopTool(props, storeRef);
213216

214-
const tools = [exportTool, openInDesktopTool];
217+
const tools = [runcellTool, exportTool, openInDesktopTool];
215218
if (props.env && ["jupyter_widgets", "streamlit", "gradio", "marimo", "anywidget", "web_server"].indexOf(props.env) !== -1 && props.useSaveTool) {
216219
const saveTool = getSaveTool(props, gwRef, storeRef, isChanged, setIsChanged);
217220
tools.push(saveTool);
@@ -228,20 +231,25 @@ const ExploreApp: React.FC<IAppProps & {initChartFlag: boolean}> = (props) => {
228231

229232
const enhanceAPI = React.useMemo(() => {
230233
if (props.showCloudTool) {
231-
return {
232-
features: {
233-
"askviz": async (metas: IViewField[], query: string) => {
234-
const resp = await communicationStore.comm?.sendMsg("get_spec_by_text", { metas, query });
235-
return resp?.data.data;
236-
},
237-
"vlChat": async (metas: IViewField[], chats: IChatMessage[]) => {
238-
const resp = await communicationStore.comm?.sendMsg("get_chart_by_chats", { metas, chats });
239-
return resp?.data.data;
240-
}
241-
}
234+
const features: Record<string, any> = {};
235+
if (props.enableAskViz) {
236+
features["askviz"] = async (metas: IViewField[], query: string) => {
237+
const resp = await communicationStore.comm?.sendMsg("get_spec_by_text", { metas, query });
238+
return resp?.data.data;
239+
};
240+
}
241+
if (props.enableVlChat) {
242+
features["vlChat"] = async (metas: IViewField[], chats: IChatMessage[]) => {
243+
const resp = await communicationStore.comm?.sendMsg("get_chart_by_chats", { metas, chats });
244+
return resp?.data.data;
245+
};
246+
}
247+
if (Object.keys(features).length > 0) {
248+
return { features };
242249
}
243250
}
244-
}, [props.showCloudTool]);
251+
return undefined;
252+
}, [props.showCloudTool, props.enableAskViz, props.enableVlChat]);
245253

246254
const computationCallback = React.useMemo(() => getComputationCallback(props), []);
247255

@@ -401,6 +409,7 @@ function GWalkerComponent(props: IAppProps) {
401409

402410
return (
403411
<React.StrictMode>
412+
<RuncellBanner env={props.env} />
404413
{ props.gwMode === "explore" && <ExploreApp {...props} dataSource={dataSource} initChartFlag={initChartFlag} /> }
405414
{ props.gwMode === "renderer" && <PureRednererApp {...props} dataSource={dataSource} /> }
406415
{ props.gwMode === "filter_renderer" && <GraphicRendererApp {...props} dataSource={dataSource} /> }
@@ -430,12 +439,14 @@ function GWalker(props: IAppProps, id: string) {
430439
}
431440

432441
preRender(props).then(() => {
433-
ReactDOM.render(
434-
<MainApp darkMode={props.dark} gid={props.id} sendMessage={props.env?.startsWith("jupyter")}>
435-
<GWalkerComponent {...props} />
436-
</MainApp>,
437-
document.getElementById(id)
438-
);
442+
const container = document.getElementById(id);
443+
if (container) {
444+
createRoot(container).render(
445+
<MainApp darkMode={props.dark} gid={props.id} sendMessage={props.env?.startsWith("jupyter")}>
446+
<GWalkerComponent {...props} />
447+
</MainApp>
448+
);
449+
}
439450
})
440451
}
441452

@@ -447,22 +458,26 @@ function PreviewApp(props: IPreviewProps, containerId: string) {
447458
window.document.getElementById(containerId)?.remove();
448459
}
449460

450-
ReactDOM.render(
451-
<MainApp darkMode={props.dark} hideToolBar>
452-
<Preview {...props} />
453-
</MainApp>,
454-
document.getElementById(containerId)
455-
);
461+
const container = document.getElementById(containerId);
462+
if (container) {
463+
createRoot(container).render(
464+
<MainApp darkMode={props.dark} hideToolBar>
465+
<Preview {...props} />
466+
</MainApp>
467+
);
468+
}
456469
}
457470

458471
function ChartPreviewApp(props: IChartPreviewProps, id: string) {
459472
props.visSpec = FormatSpec([props.visSpec], [])[0];
460-
ReactDOM.render(
461-
<MainApp darkMode={props.dark} hideToolBar>
462-
<ChartPreview {...props} />
463-
</MainApp>,
464-
document.getElementById(id)
465-
);
473+
const container = document.getElementById(id);
474+
if (container) {
475+
createRoot(container).render(
476+
<MainApp darkMode={props.dark} hideToolBar>
477+
<ChartPreview {...props} />
478+
</MainApp>
479+
);
480+
}
466481
}
467482

468483
function GraphicRendererApp(props: IAppProps) {
@@ -539,7 +554,7 @@ function TableWalkerApp(props: IAppProps) {
539554
function SteamlitGWalkerApp(streamlitProps: any) {
540555
const props = streamlitProps.args as IAppProps;
541556
const [inited, setInited] = useState(false);
542-
const container = React.useRef(null);
557+
const container = React.useRef<HTMLDivElement>(null);
543558
props.visSpec = FormatSpec(props.visSpec, props.rawFields);
544559

545560
useEffect(() => {
@@ -572,13 +587,15 @@ function SteamlitGWalkerApp(streamlitProps: any) {
572587
};
573588

574589
const StreamlitGWalker = () => {
575-
const StreamlitGWalker = withStreamlitConnection(SteamlitGWalkerApp);
576-
ReactDOM.render(
577-
<React.StrictMode>
578-
<StreamlitGWalker />
579-
</React.StrictMode>,
580-
document.getElementById("root")
581-
)
590+
const StreamlitGWalkerComponent = withStreamlitConnection(SteamlitGWalkerApp);
591+
const container = document.getElementById("root");
592+
if (container) {
593+
createRoot(container).render(
594+
<React.StrictMode>
595+
<StreamlitGWalkerComponent />
596+
</React.StrictMode>
597+
);
598+
}
582599
}
583600

584601
function AnywidgetGWalkerApp() {

app/src/interfaces/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export interface IAppProps {
1919
needLoadDatas?: boolean;
2020
specType: string;
2121
showCloudTool: boolean;
22+
enableAskViz: boolean;
23+
enableVlChat: boolean;
2224
needInitChart: boolean;
2325
useKernelCalc: boolean;
2426
useSaveTool: boolean;

0 commit comments

Comments
 (0)