@hedeqiang/dsh-nl2sql 0.14.0 → 0.14.1

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/CHANGELOG.md CHANGED
@@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.14.1] - 2026-11-13
9
+
10
+ ### Fixed
11
+ - Charts, the ER graph and forecast charts were blurry on Retina / HiDPI screens: ECharts runs in a sandboxed chunk without access to the real `window`, so it rendered at 1×. Charts now render at the screen's device pixel ratio (re-rendering when it changes, e.g. moving to another monitor or zooming), and PNG export uses at least 2×.
12
+
8
13
  ## [0.14.0] - 2026-11-13
9
14
 
10
15
  ### Added
@@ -139,7 +144,8 @@ These features are tested with local databases and fake IM / Lark servers; real
139
144
 
140
145
  - Redesigned UI; Lark push (Markdown and native charts); access control, feedback loop, dashboard global filters; PostgreSQL / Elasticsearch, SSH tunnels, privacy settings, automatic overviews, scheduled reports; semantic layer, ER graph, scan guard, history and audit, dashboards; first version.
141
146
 
142
- [Unreleased]: https://github.com/hedeqiang/dsh-nl2sql/compare/v0.14.0...HEAD
147
+ [Unreleased]: https://github.com/hedeqiang/dsh-nl2sql/compare/v0.14.1...HEAD
148
+ [0.14.1]: https://github.com/hedeqiang/dsh-nl2sql/compare/v0.14.0...v0.14.1
143
149
  [0.14.0]: https://github.com/hedeqiang/dsh-nl2sql/compare/v0.13.1...v0.14.0
144
150
  [0.13.1]: https://github.com/hedeqiang/dsh-nl2sql/compare/v0.13.0...v0.13.1
145
151
  [0.13.0]: https://github.com/hedeqiang/dsh-nl2sql/compare/v0.12.2...v0.13.0
package/lib/client.js CHANGED
@@ -2836,6 +2836,26 @@ window.__ModuleLoader__.load({
2836
2836
  }
2837
2837
  // ----------------------------------------------------------- charts
2838
2838
 
2839
+ // ECharts is loaded in a sandboxed wrapper (lib/client.echarts.js) whose `window` is an empty
2840
+ // object, so it cannot read the screen's devicePixelRatio and would draw at 1x (blurry on
2841
+ // Retina / HiDPI). Every chart passes the real ratio explicitly and re-inits when it changes
2842
+ // (moving between screens, browser zoom).
2843
+ const chartDpr = () => Math.max(1, Math.min(3, (typeof window !== 'undefined' && window.devicePixelRatio) || 1))
2844
+ function initChart(echarts, el, opts = {}) {
2845
+ return echarts.init(el, null, { renderer: 'canvas', devicePixelRatio: chartDpr(), ...opts })
2846
+ }
2847
+ /** Call `onChange` when the device pixel ratio changes; returns a cleanup. */
2848
+ function watchDpr(onChange) {
2849
+ if (typeof window === 'undefined' || !window.matchMedia) return () => {}
2850
+ let mq
2851
+ const listen = () => {
2852
+ mq = window.matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`)
2853
+ mq.addEventListener?.('change', handler, { once: true })
2854
+ }
2855
+ const handler = () => { onChange(); listen() }
2856
+ listen()
2857
+ return () => mq?.removeEventListener?.('change', handler)
2858
+ }
2839
2859
  let echartsPromise
2840
2860
  function loadEcharts() {
2841
2861
  echartsPromise ??= require.async('./client.echarts.js')
@@ -2942,17 +2962,27 @@ window.__ModuleLoader__.load({
2942
2962
  useEffect(() => {
2943
2963
  let disposed = false
2944
2964
  let observer
2965
+ let stopDpr
2945
2966
  loadEcharts().then((echarts) => {
2946
2967
  if (disposed || !ref.current) return
2947
- instance.current = echarts.init(ref.current, null, { renderer: 'canvas' })
2948
- instance.current.on('click', (p) => { if (pointRef.current && p.name !== undefined && p.name !== '') pointRef.current(p.name) })
2968
+ const create = () => {
2969
+ const prev = instance.current
2970
+ const option = prev?.getOption?.()
2971
+ prev?.dispose()
2972
+ instance.current = initChart(echarts, ref.current)
2973
+ instance.current.on('click', (p) => { if (pointRef.current && p.name !== undefined && p.name !== '') pointRef.current(p.name) })
2974
+ if (option) instance.current.setOption(option, true)
2975
+ onInstance?.(instance.current)
2976
+ }
2977
+ create()
2978
+ stopDpr = watchDpr(() => { if (!disposed && ref.current) create() })
2949
2979
  observer = new ResizeObserver(() => instance.current?.resize())
2950
2980
  observer.observe(ref.current)
2951
2981
  setReady(true)
2952
- onInstance?.(instance.current)
2953
2982
  }, (e) => setError(e.message))
2954
2983
  return () => {
2955
2984
  disposed = true
2985
+ stopDpr?.()
2956
2986
  observer?.disconnect()
2957
2987
  instance.current?.dispose()
2958
2988
  instance.current = null
@@ -3095,7 +3125,7 @@ window.__ModuleLoader__.load({
3095
3125
  const [flash, setFlash] = useFlash()
3096
3126
  const copySql = () => { navigator.clipboard?.writeText(result.sql); setFlash(t('result.copied')) }
3097
3127
  const exportPng = () => {
3098
- const url = chartInstance?.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: cssVar(document.body, '--dsw-alias-bg-base', '#fff') })
3128
+ const url = chartInstance?.getDataURL({ type: 'png', pixelRatio: Math.max(2, chartDpr()), backgroundColor: cssVar(document.body, '--dsw-alias-bg-base', '#fff') })
3099
3129
  if (url) fetch(url).then(r => r.blob()).then(b => download(`${safeName(result.title)}.png`, b))
3100
3130
  }
3101
3131
  const isChart = chart.type !== 'table' && chart.type !== 'kpi'
@@ -3761,7 +3791,7 @@ window.__ModuleLoader__.load({
3761
3791
  let observer
3762
3792
  loadEcharts().then((echarts) => {
3763
3793
  if (disposed || !ref.current) return
3764
- chart = echarts.init(ref.current, null, { renderer: 'canvas' })
3794
+ chart = initChart(echarts, ref.current)
3765
3795
  observer = new ResizeObserver(() => chart?.resize())
3766
3796
  observer.observe(ref.current)
3767
3797
  const el = ref.current
@@ -7227,7 +7257,7 @@ window.__ModuleLoader__.load({
7227
7257
  let observer
7228
7258
  loadEcharts().then((echarts) => {
7229
7259
  if (disposed || !ref.current) return
7230
- inst.current = echarts.init(ref.current, null, { renderer: 'canvas' })
7260
+ inst.current = initChart(echarts, ref.current)
7231
7261
  observer = new ResizeObserver(() => inst.current?.resize())
7232
7262
  observer.observe(ref.current)
7233
7263
  try { inst.current.setOption(proForecastOption(forecast, ref.current, t), true) } catch (e) { setError(e.message) }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hedeqiang/dsh-nl2sql",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "Ask MySQL, PostgreSQL, Elasticsearch and CSV/Excel in plain language inside DeepSeek Harness: charts, dashboards, alerts, and IM push and bots for Lark/Feishu, Telegram, WeCom and DingTalk.",
5
5
  "type": "module",
6
6
  "bin": {