From 29c6d1fbede5e72d2da33ccfd6b8b13273057504 Mon Sep 17 00:00:00 2001 From: Abeautifulsnow Date: Mon, 7 Sep 2026 14:03:57 +0800 Subject: [PATCH] feat(ai): render chart-json blocks as interactive charts --- .../components/ai/rich/AiChartRenderer.vue | 143 +++++++++ .../__tests__/AiChartRendererLayout.spec.ts | 29 ++ .../src/components/editor/AiAssistant.vue | 15 +- apps/desktop/src/i18n/locales/en.ts | 3 + apps/desktop/src/i18n/locales/es.ts | 3 + apps/desktop/src/i18n/locales/it.ts | 3 + apps/desktop/src/i18n/locales/ja.ts | 3 + apps/desktop/src/i18n/locales/ko.ts | 3 + apps/desktop/src/i18n/locales/pt-BR.ts | 3 + apps/desktop/src/i18n/locales/tr.ts | 3 + apps/desktop/src/i18n/locales/zh-CN.ts | 3 + apps/desktop/src/i18n/locales/zh-TW.ts | 3 + apps/desktop/src/lib/__tests__/ai/ai.spec.ts | 39 +++ .../lib/__tests__/ai/aiMessageRender.spec.ts | 57 +++- apps/desktop/src/lib/ai/ai.ts | 46 ++- apps/desktop/src/lib/ai/aiMessageRender.ts | 35 ++- .../__tests__/aiChartOption.spec.ts | 114 +++++++ .../richContent/__tests__/aiChartSpec.spec.ts | 248 +++++++++++++++ .../__tests__/aiRichContent.spec.ts | 45 +++ .../src/lib/ai/richContent/aiChartOption.ts | 73 +++++ .../src/lib/ai/richContent/aiChartSpec.ts | 284 ++++++++++++++++++ .../src/lib/ai/richContent/aiRichContent.ts | 68 +++++ 22 files changed, 1214 insertions(+), 9 deletions(-) create mode 100644 apps/desktop/src/components/ai/rich/AiChartRenderer.vue create mode 100644 apps/desktop/src/components/ai/rich/__tests__/AiChartRendererLayout.spec.ts create mode 100644 apps/desktop/src/lib/ai/richContent/__tests__/aiChartOption.spec.ts create mode 100644 apps/desktop/src/lib/ai/richContent/__tests__/aiChartSpec.spec.ts create mode 100644 apps/desktop/src/lib/ai/richContent/__tests__/aiRichContent.spec.ts create mode 100644 apps/desktop/src/lib/ai/richContent/aiChartOption.ts create mode 100644 apps/desktop/src/lib/ai/richContent/aiChartSpec.ts create mode 100644 apps/desktop/src/lib/ai/richContent/aiRichContent.ts diff --git a/apps/desktop/src/components/ai/rich/AiChartRenderer.vue b/apps/desktop/src/components/ai/rich/AiChartRenderer.vue new file mode 100644 index 0000000000..bcffcacc00 --- /dev/null +++ b/apps/desktop/src/components/ai/rich/AiChartRenderer.vue @@ -0,0 +1,143 @@ + + + diff --git a/apps/desktop/src/components/ai/rich/__tests__/AiChartRendererLayout.spec.ts b/apps/desktop/src/components/ai/rich/__tests__/AiChartRendererLayout.spec.ts new file mode 100644 index 0000000000..b4146cae51 --- /dev/null +++ b/apps/desktop/src/components/ai/rich/__tests__/AiChartRendererLayout.spec.ts @@ -0,0 +1,29 @@ +import { readFileSync } from "node:fs"; +import { describe, expect, it } from "vitest"; + +const rendererSource = readFileSync(new URL("../AiChartRenderer.vue", import.meta.url), "utf8"); + +describe("AiChartRenderer layout", () => { + it("establishes the chart height on the parent that vue-echarts fills", () => { + expect(rendererSource).toContain("min-h-60 h-[clamp(15rem,35vw,20rem)]"); + expect(rendererSource).toContain('class="min-h-0 flex-1"'); + expect(rendererSource).toContain('class="h-full w-full p-2"'); + expect(rendererSource).not.toContain('class="h-80 w-full p-2"'); + }); + + it("uses the native save dialog and binary writer for PNGs in Tauri", () => { + expect(rendererSource).toContain('await import("@tauri-apps/plugin-dialog")'); + expect(rendererSource).toContain('await import("@tauri-apps/plugin-fs")'); + expect(rendererSource).toContain("await writeFile(path, new Uint8Array(await blob.arrayBuffer()))"); + }); + + it("surfaces download failures through a localized toast instead of a hardcoded English tooltip", () => { + expect(rendererSource).toContain("t('ai.chartDownloadPng')"); + expect(rendererSource).toContain('toast(t("ai.chartDownloadFailed"))'); + const savePath = rendererSource.slice(rendererSource.indexOf("await writeFile(path, new Uint8Array(await blob.arrayBuffer()))")); + expect(savePath).toContain('} catch {\n toast(t("ai.chartDownloadFailed"));\n }'); + expect(rendererSource).not.toContain('title="Download PNG"'); + expect(rendererSource).not.toContain('aria-label="Download PNG"'); + expect(rendererSource).toContain('t("ai.chartData")'); + }); +}); diff --git a/apps/desktop/src/components/editor/AiAssistant.vue b/apps/desktop/src/components/editor/AiAssistant.vue index 92e692bd87..fc0304ee25 100644 --- a/apps/desktop/src/components/editor/AiAssistant.vue +++ b/apps/desktop/src/components/editor/AiAssistant.vue @@ -1,5 +1,5 @@