diff --git a/README.md b/README.md index b512473..3c82164 100644 --- a/README.md +++ b/README.md @@ -24,12 +24,13 @@ A collection of enhancements, plugins, and prompts for [open-webui](https://gith | Rank | Plugin | Version | Downloads | Views | 📅 Updated | | :---: | :--- | :---: | :---: | :---: | :---: | | 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![p1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_dl.json&style=flat) | ![p1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | -| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![p2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_dl.json&style=flat) | ![p2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | +| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | ![v](https://img.shields.io/badge/v-1.6.0-blue?style=flat) | ![p2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_dl.json&style=flat) | ![p2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | | 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | ![v](https://img.shields.io/badge/v-1.2.8-blue?style=flat) | ![p3_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_dl.json&style=flat) | ![p3_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | -| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![p4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_dl.json&style=flat) | ![p4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | +| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | ![v](https://img.shields.io/badge/v-1.6.0-blue?style=flat) | ![p4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_dl.json&style=flat) | ![p4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | | 5️⃣ | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![p5_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_dl.json&style=flat) | ![p5_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | | 6️⃣ | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![p6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_dl.json&style=flat) | ![p6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | + ### 📈 Total Downloads Trend ![Activity](https://gist.githubusercontent.com/Fu-Jie/db3d95687075a880af6f1fba76d679c6/raw/chart.svg) diff --git a/README_CN.md b/README_CN.md index 703f08f..a3ce581 100644 --- a/README_CN.md +++ b/README_CN.md @@ -21,12 +21,13 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词 | 排名 | 插件 | 版本 | 下载 | 浏览 | 📅 更新 | | :---: | :--- | :---: | :---: | :---: | :---: | | 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![p1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_dl.json&style=flat) | ![p1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | -| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![p2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_dl.json&style=flat) | ![p2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | +| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | ![v](https://img.shields.io/badge/v-1.6.0-blue?style=flat) | ![p2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_dl.json&style=flat) | ![p2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | | 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | ![v](https://img.shields.io/badge/v-1.2.8-blue?style=flat) | ![p3_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_dl.json&style=flat) | ![p3_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | -| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![p4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_dl.json&style=flat) | ![p4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | +| 4️⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | ![v](https://img.shields.io/badge/v-1.6.0-blue?style=flat) | ![p4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_dl.json&style=flat) | ![p4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | | 5️⃣ | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![p5_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_dl.json&style=flat) | ![p5_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | | 6️⃣ | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![p6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_dl.json&style=flat) | ![p6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--22-gray?style=flat) | + ### 📈 总下载量累计趋势 ![Activity](https://gist.githubusercontent.com/Fu-Jie/db3d95687075a880af6f1fba76d679c6/raw/chart.svg) diff --git a/docs/plugins/actions/index.md b/docs/plugins/actions/index.md index 48e5d38..3b75fbb 100644 --- a/docs/plugins/actions/index.md +++ b/docs/plugins/actions/index.md @@ -33,7 +33,7 @@ Actions are interactive plugins that: Transform text into professional infographics using AntV visualization engine with various templates. - **Version:** 1.5.0 + **Version:** 1.6.0 [:octicons-arrow-right-24: Documentation](smart-infographic.md) diff --git a/docs/plugins/actions/index.zh.md b/docs/plugins/actions/index.zh.md index 708f17b..a0b26fe 100644 --- a/docs/plugins/actions/index.zh.md +++ b/docs/plugins/actions/index.zh.md @@ -33,7 +33,7 @@ Actions 是交互式插件,能够: 使用 AntV 可视化引擎,将文本转成专业的信息图。 - **版本:** 1.4.9 + **版本:** 1.6.0 [:octicons-arrow-right-24: 查看文档](smart-infographic.md) diff --git a/docs/plugins/actions/infographic.md b/docs/plugins/actions/infographic.md index 987978c..1acaa25 100644 --- a/docs/plugins/actions/infographic.md +++ b/docs/plugins/actions/infographic.md @@ -1,6 +1,6 @@ # Smart Infographic -| By [Fu-Jie](https://github.com/Fu-Jie) · v1.5.0 | [⭐ Star this repo](https://github.com/Fu-Jie/openwebui-extensions) | +| By [Fu-Jie](https://github.com/Fu-Jie) · v1.6.0 | [⭐ Star this repo](https://github.com/Fu-Jie/openwebui-extensions) | | :--- | ---: | | ![followers](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_followers.json&label=%F0%9F%91%A5&style=flat) | ![points](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_points.json&label=%E2%AD%90&style=flat) | ![top](https://img.shields.io/badge/%F0%9F%8F%86-Top%20%3C1%25-10b981?style=flat) | ![contributions](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_contributions.json&label=%F0%9F%93%A6&style=flat) | ![downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_downloads.json&label=%E2%AC%87%EF%B8%8F&style=flat) | ![saves](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_saves.json&label=%F0%9F%92%BE&style=flat) | ![views](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_views.json&label=%F0%9F%91%81%EF%B8%8F&style=flat) | @@ -8,7 +8,7 @@ An Open WebUI plugin powered by the AntV Infographic engine. It transforms long text into professional, beautiful infographics with a single click. -## 🔥 What's New in v1.5.0 +## 🔥 What's New in v1.6.0 - 🌐 **Smart Language Detection**: Automatically detects the accurate UI language from your browser. - 🗣️ **Context-Aware Generation**: Generated infographics now strictly follow the language of your input content (e.g., input Japanese -> output Japanese infographic). diff --git a/docs/plugins/actions/infographic.zh.md b/docs/plugins/actions/infographic.zh.md index c897029..de3df0b 100644 --- a/docs/plugins/actions/infographic.zh.md +++ b/docs/plugins/actions/infographic.zh.md @@ -1,6 +1,6 @@ # 智能信息图 -| 作者:[Fu-Jie](https://github.com/Fu-Jie) · v1.5.0 | [⭐ 点个 Star 支持项目](https://github.com/Fu-Jie/openwebui-extensions) | +| 作者:[Fu-Jie](https://github.com/Fu-Jie) · v1.6.0 | [⭐ 点个 Star 支持项目](https://github.com/Fu-Jie/openwebui-extensions) | | :--- | ---: | | ![followers](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_followers.json&label=%F0%9F%91%A5&style=flat) | ![points](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_points.json&label=%E2%AD%90&style=flat) | ![top](https://img.shields.io/badge/%F0%9F%8F%86-Top%20%3C1%25-10b981?style=flat) | ![contributions](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_contributions.json&label=%F0%9F%93%A6&style=flat) | ![downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_downloads.json&label=%E2%AC%87%EF%B8%8F&style=flat) | ![saves](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_saves.json&label=%F0%9F%92%BE&style=flat) | ![views](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_views.json&label=%F0%9F%91%81%EF%B8%8F&style=flat) | @@ -8,7 +8,7 @@ 基于 AntV Infographic 引擎的 Open WebUI 插件,能够将长文本内容一键转换为专业、美观的信息图表。 -## 🔥 最新更新 v1.5.0 +## 🔥 最新更新 v1.6.0 - 🌐 **智能语言检测**:自动从浏览器准确识别当前界面语言设置。 - 🗣️ **上下文感知生成**:生成的信息图内容现在严格跟随用户输入内容的语言(例如:输入日语 -> 生成日语信息图)。 diff --git a/plugins/actions/infographic/README.md b/plugins/actions/infographic/README.md index 9742706..a711693 100644 --- a/plugins/actions/infographic/README.md +++ b/plugins/actions/infographic/README.md @@ -1,6 +1,6 @@ # Smart Infographic -| By [Fu-Jie](https://github.com/Fu-Jie) · v1.5.0 | [⭐ Star this repo](https://github.com/Fu-Jie/openwebui-extensions) | +| By [Fu-Jie](https://github.com/Fu-Jie) · v1.6.0 | [⭐ Star this repo](https://github.com/Fu-Jie/openwebui-extensions) | | :--- | ---: | | ![followers](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_followers.json&label=%F0%9F%91%A5&style=flat) | ![points](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_points.json&label=%E2%AD%90&style=flat) | ![top](https://img.shields.io/badge/%F0%9F%8F%86-Top%20%3C1%25-10b981?style=flat) | ![contributions](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_contributions.json&label=%F0%9F%93%A6&style=flat) | ![downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_downloads.json&label=%E2%AC%87%EF%B8%8F&style=flat) | ![saves](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_saves.json&label=%F0%9F%92%BE&style=flat) | ![views](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_views.json&label=%F0%9F%91%81%EF%B8%8F&style=flat) | @@ -21,7 +21,7 @@ When the selection dialog opens, search for this plugin, check it, and continue. > [!IMPORTANT] > If the official OpenWebUI Community version is already installed, remove it first. After that, Batch Install Plugins can keep this plugin updated in future runs. -## 🔥 What's New in v1.5.0 +## 🔥 What's New in v1.6.0 - 🌐 **Smart Language Detection**: Automatically detects the accurate UI language from your browser. - 🗣️ **Context-Aware Generation**: Generated infographics now strictly follow the language of your input content (e.g., input Japanese -> output Japanese infographic). diff --git a/plugins/actions/infographic/README_CN.md b/plugins/actions/infographic/README_CN.md index e684764..02a054d 100644 --- a/plugins/actions/infographic/README_CN.md +++ b/plugins/actions/infographic/README_CN.md @@ -1,6 +1,6 @@ # 智能信息图 -| 作者:[Fu-Jie](https://github.com/Fu-Jie) · v1.5.0 | [⭐ 点个 Star 支持项目](https://github.com/Fu-Jie/openwebui-extensions) | +| 作者:[Fu-Jie](https://github.com/Fu-Jie) · v1.6.0 | [⭐ 点个 Star 支持项目](https://github.com/Fu-Jie/openwebui-extensions) | | :--- | ---: | | ![followers](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_followers.json&label=%F0%9F%91%A5&style=flat) | ![points](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_points.json&label=%E2%AD%90&style=flat) | ![top](https://img.shields.io/badge/%F0%9F%8F%86-Top%20%3C1%25-10b981?style=flat) | ![contributions](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_contributions.json&label=%F0%9F%93%A6&style=flat) | ![downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_downloads.json&label=%E2%AC%87%EF%B8%8F&style=flat) | ![saves](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_saves.json&label=%F0%9F%92%BE&style=flat) | ![views](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_views.json&label=%F0%9F%91%81%EF%B8%8F&style=flat) | @@ -21,7 +21,7 @@ > [!IMPORTANT] > 如果你已经安装了 OpenWebUI 官方社区里的同名版本,请先删除旧版本,否则重新安装时可能报错。删除后,Batch Install Plugins 后续就可以继续负责更新这个插件。 -## 🔥 最新更新 v1.5.0 +## 🔥 最新更新 v1.6.0 - 🌐 **智能语言检测**:自动从浏览器准确识别当前界面语言设置。 - 🗣️ **上下文感知生成**:生成的信息图内容现在严格跟随用户输入内容的语言(例如:输入日语 -> 生成日语信息图)。 diff --git a/plugins/actions/infographic/infographic.py b/plugins/actions/infographic/infographic.py index f6c2fa7..8049de1 100644 --- a/plugins/actions/infographic/infographic.py +++ b/plugins/actions/infographic/infographic.py @@ -4,7 +4,7 @@ author: Fu-Jie author_url: https://github.com/Fu-Jie/openwebui-extensions funding_url: https://github.com/open-webui icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4= -version: 1.5.0 +version: 1.6.0 openwebui_id: ad6f0c7f-c571-4dea-821d-8e71697274cf description: AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads. """ @@ -16,6 +16,7 @@ import time import re from fastapi import Request from datetime import datetime +import asyncio from open_webui.utils.chat import generate_chat_completion from open_webui.models.users import Users @@ -25,6 +26,477 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) +TRANSLATIONS = { + "en-US": { + "status_starting": "Smart Infographic is starting, generating infographic for you...", + "error_no_content": "Unable to retrieve valid user message content.", + "error_text_too_short": "Text content is too short ({len} characters), unable to perform effective analysis. Please provide at least {min_len} characters of text.", + "status_analyzing": "Smart Infographic: Analyzing text structure in depth...", + "status_drawing": "Smart Infographic: Drawing completed!", + "notification_success": "Mind map has been generated, {user_name}!", + "error_processing": "Smart Infographic processing failed: {error}", + "error_user_facing": "Sorry, Smart Infographic encountered an error during processing: {error}.\nPlease check the Open WebUI backend logs for more details.", + "status_failed": "Smart Infographic: Processing failed.", + "notification_failed": "Smart Infographic generation failed, {user_name}!", + "status_rendering_image": "Smart Infographic: Rendering image...", + "status_image_generated": "Smart Infographic: Image generated!", + "notification_image_success": "Mind map image has been generated, {user_name}!", + "ui_title": "🧠 Smart Infographic", + "ui_user": "User:", + "ui_time": "Time:", + "ui_download_png": "PNG", + "ui_download_svg": "SVG", + "ui_download_md": "Markdown", + "ui_zoom_out": "-", + "ui_zoom_reset": "Reset", + "ui_zoom_in": "+", + "ui_depth_select": "Expand Level", + "ui_depth_all": "Expand All", + "ui_depth_2": "Level 2", + "ui_depth_3": "Level 3", + "ui_fullscreen": "Fullscreen", + "ui_theme": "Theme", + "ui_footer": "Powered by Markmap", + "html_error_missing_content": "⚠️ Unable to load infographic: Missing valid content.", + "html_error_load_failed": "⚠️ Resource loading failed, please try again later.", + "js_done": "Done", + "js_failed": "Failed", + "js_generating": "Generating...", + "js_filename": "infographic.png", + "js_upload_failed": "Upload failed: ", + "md_image_alt": "🧠 Infographic", + }, + "zh-CN": { + "status_starting": "信息图已启动,正在为您生成信息图...", + "error_no_content": "无法获取有效的用户消息内容。", + "error_text_too_short": "文本内容过短({len}字符),无法进行有效分析。请提供至少{min_len}字符的文本。", + "status_analyzing": "信息图:深入分析文本结构...", + "status_drawing": "信息图:绘制完成!", + "notification_success": "信息图已生成,{user_name}!", + "error_processing": "信息图处理失败:{error}", + "error_user_facing": "抱歉,信息图在处理时遇到错误:{error}。\n请检查Open WebUI后端日志获取更多详情。", + "status_failed": "信息图:处理失败。", + "notification_failed": "信息图生成失败,{user_name}!", + "status_rendering_image": "信息图:正在渲染图片...", + "status_image_generated": "信息图:图片已生成!", + "notification_image_success": "信息图图片已生成,{user_name}!", + "ui_title": "🧠 智能信息图", + "ui_user": "用户:", + "ui_time": "时间:", + "ui_download_png": "PNG", + "ui_download_svg": "SVG", + "ui_download_md": "Markdown", + "ui_zoom_out": "缩小", + "ui_zoom_reset": "重置", + "ui_zoom_in": "放大", + "ui_depth_select": "展开层级", + "ui_depth_all": "全部展开", + "ui_depth_2": "展开 2 级", + "ui_depth_3": "展开 3 级", + "ui_fullscreen": "全屏", + "ui_theme": "主题", + "ui_footer": "Powered by Markmap", + "html_error_missing_content": "⚠️ 无法加载信息图:缺少有效内容。", + "html_error_load_failed": "⚠️ 资源加载失败,请稍后重试。", + "js_done": "完成", + "js_failed": "失败", + "js_generating": "生成中...", + "js_filename": "信息图.png", + "js_upload_failed": "上传失败:", + "md_image_alt": "🧠 信息图", + }, + "zh-HK": { + "status_starting": "信息圖已啟動,正在為您生成信息圖...", + "error_no_content": "無法獲取有效的用戶消息內容。", + "error_text_too_short": "文本內容過短({len}字元),無法進行有效分析。請提供至少{min_len}字元的文本。", + "status_analyzing": "信息圖:深入分析文本結構...", + "status_drawing": "信息圖:繪製完成!", + "notification_success": "信息圖已生成,{user_name}!", + "error_processing": "信息圖處理失敗:{error}", + "error_user_facing": "抱歉,信息圖在處理時遇到錯誤:{error}。\n請檢查Open WebUI後端日誌獲取更多詳情。", + "status_failed": "信息圖:處理失敗。", + "notification_failed": "信息圖生成失敗,{user_name}!", + "status_rendering_image": "信息圖:正在渲染圖片...", + "status_image_generated": "信息圖:圖片已生成!", + "notification_image_success": "信息圖圖片已生成,{user_name}!", + "ui_title": "🧠 智能信息圖", + "ui_user": "用戶:", + "ui_time": "時間:", + "ui_download_png": "PNG", + "ui_download_svg": "SVG", + "ui_download_md": "Markdown", + "ui_zoom_out": "縮小", + "ui_zoom_reset": "重置", + "ui_zoom_in": "放大", + "ui_depth_select": "展開層級", + "ui_depth_all": "全部展開", + "ui_depth_2": "展開 2 級", + "ui_depth_3": "展開 3 級", + "ui_fullscreen": "全屏", + "ui_theme": "主題", + "ui_footer": "Powered by Markmap", + "html_error_missing_content": "⚠️ 無法加載信息圖:缺少有效內容。", + "html_error_load_failed": "⚠️ 資源加載失敗,請稍後重試。", + "js_done": "完成", + "js_failed": "失敗", + "js_generating": "生成中...", + "js_filename": "信息圖.png", + "js_upload_failed": "上傳失敗:", + "md_image_alt": "🧠 信息圖", + }, + "zh-TW": { + "status_starting": "信息圖已啟動,正在為您生成信息圖...", + "error_no_content": "無法獲取有效的用戶消息內容。", + "error_text_too_short": "文本內容過短({len}字元),無法進行有效分析。請提供至少{min_len}字元的文本。", + "status_analyzing": "信息圖:深入分析文本結構...", + "status_drawing": "信息圖:繪製完成!", + "notification_success": "信息圖已生成,{user_name}!", + "error_processing": "信息圖處理失敗:{error}", + "error_user_facing": "抱歉,信息圖在處理時遇到錯誤:{error}。\n請檢查Open WebUI後端日誌獲取更多詳情。", + "status_failed": "信息圖:處理失敗。", + "notification_failed": "信息圖生成失敗,{user_name}!", + "status_rendering_image": "信息圖:正在渲染圖片...", + "status_image_generated": "信息圖:圖片已生成!", + "notification_image_success": "信息圖圖片已生成,{user_name}!", + "ui_title": "🧠 智能信息圖", + "ui_user": "用戶:", + "ui_time": "時間:", + "ui_download_png": "PNG", + "ui_download_svg": "SVG", + "ui_download_md": "Markdown", + "ui_zoom_out": "縮小", + "ui_zoom_reset": "重置", + "ui_zoom_in": "放大", + "ui_depth_select": "展開層級", + "ui_depth_all": "全部展開", + "ui_depth_2": "展開 2 級", + "ui_depth_3": "展開 3 級", + "ui_fullscreen": "全屏", + "ui_theme": "主題", + "ui_footer": "Powered by Markmap", + "html_error_missing_content": "⚠️ 無法加載信息圖:缺少有效內容。", + "html_error_load_failed": "⚠️ 資源加載失敗,請稍後重試。", + "js_done": "完成", + "js_failed": "失敗", + "js_generating": "生成中...", + "js_filename": "信息圖.png", + "js_upload_failed": "上傳失敗:", + "md_image_alt": "🧠 信息圖", + }, + "ko-KR": { + "status_starting": "스마트 마인드맵이 시작되었습니다, 마인드맵을 생성 중입니다...", + "error_no_content": "유효한 사용자 메시지 내용을 가져올 수 없습니다.", + "error_text_too_short": "텍스트 내용이 너무 짧아({len}자), 효과적인 분석을 수행할 수 없습니다. 최소 {min_len}자 이상의 텍스트를 제공해 주세요.", + "status_analyzing": "스마트 마인드맵: 텍스트 구조 심층 분석 중...", + "status_drawing": "스마트 마인드맵: 그리기 완료!", + "notification_success": "마인드맵이 생성되었습니다, {user_name}님!", + "error_processing": "스마트 마인드맵 처리 실패: {error}", + "error_user_facing": "죄송합니다, 스마트 마인드맵 처리 중 오류가 발생했습니다: {error}.\n자세한 내용은 Open WebUI 백엔드 로그를 확인해 주세요.", + "status_failed": "스마트 마인드맵: 처리 실패.", + "notification_failed": "스마트 마인드맵 생성 실패, {user_name}님!", + "status_rendering_image": "스마트 마인드맵: 이미지 렌더링 중...", + "status_image_generated": "스마트 마인드맵: 이미지 생성됨!", + "notification_image_success": "마인드맵 이미지가 생성되었습니다, {user_name}님!", + "ui_title": "🧠 스마트 마인드맵", + "ui_user": "사용자:", + "ui_time": "시간:", + "ui_download_png": "PNG", + "ui_download_svg": "SVG", + "ui_download_md": "Markdown", + "ui_zoom_out": "-", + "ui_zoom_reset": "초기화", + "ui_zoom_in": "+", + "ui_depth_select": "레벨 확장", + "ui_depth_all": "모두 확장", + "ui_depth_2": "레벨 2", + "ui_depth_3": "레벨 3", + "ui_fullscreen": "전체 화면", + "ui_theme": "테마", + "ui_footer": "Powered by Markmap", + "html_error_missing_content": "⚠️ 마인드맵을 로드할 수 없습니다: 유효한 내용이 없습니다.", + "html_error_load_failed": "⚠️ 리소스 로드 실패, 나중에 다시 시도해 주세요.", + "js_done": "완료", + "js_failed": "실패", + "js_generating": "생성 중...", + "js_filename": "infographic.png", + "js_upload_failed": "업로드 실패: ", + "md_image_alt": "🧠 마인드맵", + }, + "ja-JP": { + "status_starting": "スマートマインドマップが起動しました。マインドマップを生成しています...", + "error_no_content": "有効なユーザーメッセージの内容を取得できませんでした。", + "error_text_too_short": "テキストの内容が短すぎるため({len}文字)、効果的な分析を実行できません。少なくとも{min_len}文字のテキストを提供してください。", + "status_analyzing": "スマートマインドマップ:テキスト構造を詳細に分析中...", + "status_drawing": "スマートマインドマップ:描画完了!", + "notification_success": "マインドマップが生成されました、{user_name}さん!", + "error_processing": "スマートマインドマップ処理失敗:{error}", + "error_user_facing": "申し訳ありません、スマートマインドマップの処理中にエラーが発生しました:{error}。\n詳細については、Open WebUIバックエンドログを確認してください。", + "status_failed": "スマートマインドマップ:処理失敗。", + "notification_failed": "スマートマインドマップ生成失敗、{user_name}さん!", + "status_rendering_image": "スマートマインドマップ:画像レンダリング中...", + "status_image_generated": "スマートマインドマップ:画像生成完了!", + "notification_image_success": "マインドマップ画像が生成されました、{user_name}さん!", + "ui_title": "🧠 スマートマインドマップ", + "ui_user": "ユーザー:", + "ui_time": "時間:", + "ui_download_png": "PNG", + "ui_download_svg": "SVG", + "ui_download_md": "Markdown", + "ui_zoom_out": "-", + "ui_zoom_reset": "リセット", + "ui_zoom_in": "+", + "ui_depth_select": "レベル展開", + "ui_depth_all": "すべて展開", + "ui_depth_2": "レベル2", + "ui_depth_3": "レベル3", + "ui_fullscreen": "全画面", + "ui_theme": "テーマ", + "ui_footer": "Powered by Markmap", + "html_error_missing_content": "⚠️ マインドマップを読み込めません:有効なコンテンツがありません。", + "html_error_load_failed": "⚠️ リソースの読み込みに失敗しました。後でもう一度お試しください。", + "js_done": "完了", + "js_failed": "失敗", + "js_generating": "生成中...", + "js_filename": "infographic.png", + "js_upload_failed": "アップロード失敗:", + "md_image_alt": "🧠 マインドマップ", + }, + "fr-FR": { + "status_starting": "Smart Infographic démarre, génération de la carte heuristique en cours...", + "error_no_content": "Impossible de récupérer le contenu valide du message utilisateur.", + "error_text_too_short": "Le contenu du texte est trop court ({len} caractères), impossible d'effectuer une analyse efficace. Veuillez fournir au moins {min_len} caractères de texte.", + "status_analyzing": "Smart Infographic : Analyse approfondie de la structure du texte...", + "status_drawing": "Smart Infographic : Dessin terminé !", + "notification_success": "La carte heuristique a été générée, {user_name} !", + "error_processing": "Échec du traitement de Smart Infographic : {error}", + "error_user_facing": "Désolé, Smart Infographic a rencontré une erreur lors du traitement : {error}.\nVeuillez vérifier les journaux backend d'Open WebUI pour plus de détails.", + "status_failed": "Smart Infographic : Échec du traitement.", + "notification_failed": "Échec de la génération de la carte heuristique, {user_name} !", + "status_rendering_image": "Smart Infographic : Rendu de l'image...", + "status_image_generated": "Smart Infographic : Image générée !", + "notification_image_success": "L'image de la carte heuristique a été générée, {user_name} !", + "ui_title": "🧠 Smart Infographic", + "ui_user": "Utilisateur :", + "ui_time": "Heure :", + "ui_download_png": "PNG", + "ui_download_svg": "SVG", + "ui_download_md": "Markdown", + "ui_zoom_out": "-", + "ui_zoom_reset": "Rénitialiser", + "ui_zoom_in": "+", + "ui_depth_select": "Niveau d'expansion", + "ui_depth_all": "Tout développer", + "ui_depth_2": "Niveau 2", + "ui_depth_3": "Niveau 3", + "ui_fullscreen": "Plein écran", + "ui_theme": "Thème", + "ui_footer": "Powered by Markmap", + "html_error_missing_content": "⚠️ Impossible de charger la carte heuristique : contenu valide manquant.", + "html_error_load_failed": "⚠️ Échec du chargement des ressources, veuillez réessayer plus tard.", + "js_done": "Terminé", + "js_failed": "Échec", + "js_generating": "Génération...", + "js_filename": "carte_heuristique.png", + "js_upload_failed": "Échec du téléchargement : ", + "md_image_alt": "🧠 Carte Heuristique", + }, + "de-DE": { + "status_starting": "Smart Infographic startet, Infographic wird für Sie erstellt...", + "error_no_content": "Gültiger Inhalt der Benutzernachricht konnte nicht abgerufen werden.", + "error_text_too_short": "Der Textinhalt ist zu kurz ({len} Zeichen), eine effektive Analyse ist nicht möglich. Bitte geben Sie mindestens {min_len} Zeichen Text an.", + "status_analyzing": "Smart Infographic: Detaillierte Analyse der Textstruktur...", + "status_drawing": "Smart Infographic: Zeichnen abgeschlossen!", + "notification_success": "Infographic wurde erstellt, {user_name}!", + "error_processing": "Smart Infographic Verarbeitung fehlgeschlagen: {error}", + "error_user_facing": "Entschuldigung, bei der Verarbeitung von Smart Infographic ist ein Fehler aufgetreten: {error}.\nBitte überprüfen Sie die Open WebUI Backend-Protokolle für weitere Details.", + "status_failed": "Smart Infographic: Verarbeitung fehlgeschlagen.", + "notification_failed": "Erstellung der Infographic fehlgeschlagen, {user_name}!", + "status_rendering_image": "Smart Infographic: Bild wird gerendert...", + "status_image_generated": "Smart Infographic: Bild erstellt!", + "notification_image_success": "Infographic-Bild wurde erstellt, {user_name}!", + "ui_title": "🧠 Smart Infographic", + "ui_user": "Benutzer:", + "ui_time": "Zeit:", + "ui_download_png": "PNG", + "ui_download_svg": "SVG", + "ui_download_md": "Markdown", + "ui_zoom_out": "-", + "ui_zoom_reset": "Zurücksetzen", + "ui_zoom_in": "+", + "ui_depth_select": "Ebene erweitern", + "ui_depth_all": "Alles erweitern", + "ui_depth_2": "Ebene 2", + "ui_depth_3": "Ebene 3", + "ui_fullscreen": "Vollbild", + "ui_theme": "Thema", + "ui_footer": "Powered by Markmap", + "html_error_missing_content": "⚠️ Infographic kann nicht geladen werden: Gültiger Inhalt fehlt.", + "html_error_load_failed": "⚠️ Ressourcenladen fehlgeschlagen, bitte versuchen Sie es später erneut.", + "js_done": "Fertig", + "js_failed": "Fehlgeschlagen", + "js_generating": "Generiere...", + "js_filename": "infographic.png", + "js_upload_failed": "Upload fehlgeschlagen: ", + "md_image_alt": "🧠 Infographic", + }, + "es-ES": { + "status_starting": "Smart Infographic se está iniciando, generando mapa mental para usted...", + "error_no_content": "No se puede recuperar el contenido válido del mensaje del usuario.", + "error_text_too_short": "El contenido del texto es demasiado corto ({len} caracteres), no se puede realizar un análisis efectivo. Proporcione al menos {min_len} caracteres de texto.", + "status_analyzing": "Smart Infographic: Analizando la estructura del texto en profundidad...", + "status_drawing": "Smart Infographic: ¡Dibujo completado!", + "notification_success": "¡El mapa mental ha sido generado, {user_name}!", + "error_processing": "Falló el procesamiento de Smart Infographic: {error}", + "error_user_facing": "Lo sentimos, Smart Infographic encontró un error durante el procesamiento: {error}.\nConsulte los registros del backend de Open WebUI para más detalles.", + "status_failed": "Smart Infographic: Procesamiento fallido.", + "notification_failed": "¡La generación del mapa mental falló, {user_name}!", + "status_rendering_image": "Smart Infographic: Renderizando imagen...", + "status_image_generated": "Smart Infographic: ¡Imagen generada!", + "notification_image_success": "¡La imagen del mapa mental ha sido generada, {user_name}!", + "ui_title": "🧠 Smart Infographic", + "ui_user": "Usuario:", + "ui_time": "Hora:", + "ui_download_png": "PNG", + "ui_download_svg": "SVG", + "ui_download_md": "Markdown", + "ui_zoom_out": "-", + "ui_zoom_reset": "Restablecer", + "ui_zoom_in": "+", + "ui_depth_select": "Expandir Nivel", + "ui_depth_all": "Expandir Todo", + "ui_depth_2": "Nivel 2", + "ui_depth_3": "Nivel 3", + "ui_fullscreen": "Pantalla completa", + "ui_theme": "Tema", + "ui_footer": "Powered by Markmap", + "html_error_missing_content": "⚠️ No se puede cargar el mapa mental: Falta contenido válido.", + "html_error_load_failed": "⚠️ Falló la carga de recursos, inténtelo de nuevo más tarde.", + "js_done": "Hecho", + "js_failed": "Fallido", + "js_generating": "Generando...", + "js_filename": "mapa_mental.png", + "js_upload_failed": "Carga fallida: ", + "md_image_alt": "🧠 Mapa Mental", + }, + "it-IT": { + "status_starting": "Smart Infographic si sta avviando, generazione mappa mentale in corso...", + "error_no_content": "Impossibile recuperare il contenuto valido del messaggio utente.", + "error_text_too_short": "Il testo è troppo breve ({len} caratteri), impossibile eseguire un'analisi efficace. Fornire almeno {min_len} caratteri di testo.", + "status_analyzing": "Smart Infographic: Analisi approfondita della struttura del testo...", + "status_drawing": "Smart Infographic: Disegno completato!", + "notification_success": "La mappa mentale è stata generata, {user_name}!", + "error_processing": "Elaborazione Smart Infographic fallita: {error}", + "error_user_facing": "Spiacenti, Smart Infographic ha riscontrato un errore durante l'elaborazione: {error}.\nControllare i log del backend di Open WebUI per ulteriori dettagli.", + "status_failed": "Smart Infographic: Elaborazione fallita.", + "notification_failed": "Generazione mappa mentale fallita, {user_name}!", + "status_rendering_image": "Smart Infographic: Rendering immagine...", + "status_image_generated": "Smart Infographic: Immagine generata!", + "notification_image_success": "L'immagine della mappa mentale è stata generata, {user_name}!", + "ui_title": "🧠 Smart Infographic", + "ui_user": "Utente:", + "ui_time": "Ora:", + "ui_download_png": "PNG", + "ui_download_svg": "SVG", + "ui_download_md": "Markdown", + "ui_zoom_out": "-", + "ui_zoom_reset": "Reimposta", + "ui_zoom_in": "+", + "ui_depth_select": "Espandi Livello", + "ui_depth_all": "Espandi Tutto", + "ui_depth_2": "Livello 2", + "ui_depth_3": "Livello 3", + "ui_fullscreen": "Schermo intero", + "ui_theme": "Tema", + "ui_footer": "Powered by Markmap", + "html_error_missing_content": "⚠️ Impossibile caricare la mappa mentale: Contenuto valido mancante.", + "html_error_load_failed": "⚠️ Caricamento risorse fallito, riprovare più tardi.", + "js_done": "Fatto", + "js_failed": "Fallito", + "js_generating": "Generazione...", + "js_filename": "mappa_mentale.png", + "js_upload_failed": "Caricamento fallito: ", + "md_image_alt": "🧠 Mappa Mentale", + }, + "vi-VN": { + "status_starting": "Smart Infographic đang khởi động, đang tạo sơ đồ tư duy cho bạn...", + "error_no_content": "Không thể lấy nội dung tin nhắn người dùng hợp lệ.", + "error_text_too_short": "Nội dung văn bản quá ngắn ({len} ký tự), không thể thực hiện phân tích hiệu quả. Vui lòng cung cấp ít nhất {min_len} ký tự văn bản.", + "status_analyzing": "Smart Infographic: Phân tích sâu cấu trúc văn bản...", + "status_drawing": "Smart Infographic: Vẽ hoàn tất!", + "notification_success": "Sơ đồ tư duy đã được tạo, {user_name}!", + "error_processing": "Xử lý Smart Infographic thất bại: {error}", + "error_user_facing": "Xin lỗi, Smart Infographic đã gặp lỗi trong quá trình xử lý: {error}.\nVui lòng kiểm tra nhật ký backend Open WebUI để biết thêm chi tiết.", + "status_failed": "Smart Infographic: Xử lý thất bại.", + "notification_failed": "Tạo sơ đồ tư duy thất bại, {user_name}!", + "status_rendering_image": "Smart Infographic: Đang render hình ảnh...", + "status_image_generated": "Smart Infographic: Hình ảnh đã tạo!", + "notification_image_success": "Hình ảnh sơ đồ tư duy đã được tạo, {user_name}!", + "ui_title": "🧠 Smart Infographic", + "ui_user": "Người dùng:", + "ui_time": "Thời gian:", + "ui_download_png": "PNG", + "ui_download_svg": "SVG", + "ui_download_md": "Markdown", + "ui_zoom_out": "-", + "ui_zoom_reset": "Đặt lại", + "ui_zoom_in": "+", + "ui_depth_select": "Mở rộng Cấp độ", + "ui_depth_all": "Mở rộng Tất cả", + "ui_depth_2": "Cấp độ 2", + "ui_depth_3": "Cấp độ 3", + "ui_fullscreen": "Toàn màn hình", + "ui_theme": "Chủ đề", + "ui_footer": "Powered by Markmap", + "html_error_missing_content": "⚠️ Không thể tải sơ đồ tư duy: Thiếu nội dung hợp lệ.", + "html_error_load_failed": "⚠️ Tải tài nguyên thất bại, vui lòng thử lại sau.", + "js_done": "Xong", + "js_failed": "Thất bại", + "js_generating": "Đang tạo...", + "js_filename": "sodo_tuduy.png", + "js_upload_failed": "Tải lên thất bại: ", + "md_image_alt": "🧠 Sơ đồ Tư duy", + }, + "id-ID": { + "status_starting": "Smart Infographic sedang dimulai, membuat peta pikiran untuk Anda...", + "error_no_content": "Tidak dapat mengambil konten pesan pengguna yang valid.", + "error_text_too_short": "Konten teks terlalu pendek ({len} karakter), tidak dapat melakukan analisis efektif. Harap berikan setidaknya {min_len} karakter teks.", + "status_analyzing": "Smart Infographic: Menganalisis struktur teks secara mendalam...", + "status_drawing": "Smart Infographic: Menggambar selesai!", + "notification_success": "Peta pikiran telah dibuat, {user_name}!", + "error_processing": "Pemrosesan Smart Infographic gagal: {error}", + "error_user_facing": "Maaf, Smart Infographic mengalami kesalahan saat memproses: {error}.\nSilakan periksa log backend Open WebUI untuk detail lebih lanjut.", + "status_failed": "Smart Infographic: Pemrosesan gagal.", + "notification_failed": "Pembuatan peta pikiran gagal, {user_name}!", + "status_rendering_image": "Smart Infographic: Merender gambar...", + "status_image_generated": "Smart Infographic: Gambar dibuat!", + "notification_image_success": "Gambar peta pikiran telah dibuat, {user_name}!", + "ui_title": "🧠 Smart Infographic", + "ui_user": "Pengguna:", + "ui_time": "Waktu:", + "ui_download_png": "PNG", + "ui_download_svg": "SVG", + "ui_download_md": "Markdown", + "ui_zoom_out": "-", + "ui_zoom_reset": "Atur Ulang", + "ui_zoom_in": "+", + "ui_depth_select": "Perluas Level", + "ui_depth_all": "Perluas Semua", + "ui_depth_2": "Level 2", + "ui_depth_3": "Level 3", + "ui_fullscreen": "Layar Penuh", + "ui_theme": "Tema", + "ui_footer": "Powered by Markmap", + "html_error_missing_content": "⚠️ Tidak dapat memuat peta pikiran: Konten valid hilang.", + "html_error_load_failed": "⚠️ Gagal memuat sumber daya, silakan coba lagi nanti.", + "js_done": "Selesai", + "js_failed": "Gagal", + "js_generating": "Membuat...", + "js_filename": "peta_pikiran.png", + "js_upload_failed": "Unggah gagal: ", + "md_image_alt": "🧠 Peta Pikiran", + }, +} + # ================================================================= # LLM Prompts # ================================================================= @@ -92,7 +564,7 @@ Choose the most appropriate template based on content structure. `chart-pie-plain-text`, `chart-pie-donut-plain-text`, `chart-wordcloud` *Other:* -`quadrant-quarter-simple-card`, `relation-circle-icon-badge` +`quadrant-quarter-simple-card`, `relation-circle-icon-badge`, `relation-dagre-flow-tb-simple-circle-node` **Text Capacity by Template Type:** - HIGH capacity (long descriptions OK): `list-column-*`, `compare-binary-*`, `sequence-timeline-*` @@ -110,6 +582,19 @@ Choose the most appropriate template based on content structure. - Format: filename without .svg, e.g., `coding`, `team-work` - Use `illus` field instead of `icon` +### 📊 Template to Data Field Mapping (CRITICAL) +For maximum rendering speed and stability, match the list identifier to the template kind structure. Do NOT just use `items` if a specific field exists: + +| Template Prefix | Data Field Identifier | Core Variables on Items | +| :--- | :--- | :--- | +| `list-*` | **`lists`** | `label`, `desc`, `icon` | +| `sequence-*` | **`sequences`** | `label`, `desc` | +| `compare-*` | **`compares`** | `label`, `value`, `children` | +| `chart-*` | **`values`** | `label`, `value` | +| `hierarchy-*` | **`root` + `children`** | 嵌套嵌套组合 | + +*Note: `items` can be used as a universal fallback adapter if template categorization is ambiguous.* + ### Data Structure Examples #### A. Standard List/Tree (Default) @@ -250,11 +735,23 @@ data ### Common Data Fields - `label`: Main title/label (Required) - `desc`: Description text -- `value`: Numeric value (for charts) +- `value`: Numeric value. **ONLY displayed on `chart-*` series templates**. For cards or lists, put data into `desc` instead. - `icon`: Icon name (e.g., `mdi/home`, `mdi/account`) or `ref:search:` - `children`: Nested items (for trees, SWOT, etc.) - `illus`: Illustration icon (specific to some templates like Quadrant) +### 📊 Data & Numeric Fields Standard +1. **Value Specification**: `value` MUST be a **pure number** (integer or float), without any symbols like `$`, `%`, or `¥`. +2. **Units Placement**: Put units or currency symbols into the `label` or `desc` instead. + - ❌ Wrong: `value $1.234` / `value 5.2%` + - ✅ Correct: `label USD ($)` -> `value 1.234` OR `label Rate` -> `desc 5.2%` + +### ⚠️ Strict Styling & Layout Rules +1. **Color Palette (`palette`)**: MUST use space-separated naked Hex values. Do NOT use quotes (`"`) or commas (`,`). + - ✅ Correct: `palette #4f46e5 #06b6d4 #10b981` + - ❌ Wrong: `palette "#4f46e5", "#06b6d4"` +2. **Binary Compare (`compare-binary-*`)**: The root of `compares` tree MUST contain **EXACTLY TWO** comparison objects. + ### Content Refinement Principles 1. **Brevity is King**: Infographics are visual. Keep text to a minimum. 2. **Title Limit**: Keep `label` (item titles) under 15 characters (approx. 10 Chinese characters). @@ -278,6 +775,7 @@ Please analyze the following text content and convert its core information into User Name: {user_name} Current Date/Time: {current_date_time_str} User Language: {user_language} +OpenWebUI Theme: {user_theme} --- **Text Content:** @@ -291,6 +789,8 @@ Please select the most appropriate infographic template based on text characteri - **Subtitle (`data.desc`):** **MUST** be ≤ **20 Chinese characters** (or ≤40 English characters). - **Card Title (`label`):** **MUST** be ≤ **6 Chinese characters** (or ≤12 English characters). Use 2-4 keywords only. - **Card Description (`desc`):** **MUST** be ≤ **12 Chinese characters** (or ≤24 English characters). Use short phrases. +- **Numeric Strictness:** `value` MUST be a pure number (no `$`, `%`, etc.). Append units to `label` or `desc` instead. +- **Dynamic Selection:** For multiple stats/currencies, use structures like `list-grid-*` or `list-row-*` for dense layouts. ⚠️ **CRITICAL**: If the original text is too long, you MUST rephrase and shorten it. Do NOT simply truncate with "...". Examples: @@ -365,10 +865,22 @@ CSS_TEMPLATE_INFOGRAPHIC = """ --ig-border-color: #e2e8f0; --ig-header-gradient: linear-gradient(135deg, #6366f1, #8b5cf6); } +.infographic-container-wrapper.dark { + --ig-primary-color: #818cf8; + --ig-secondary-color: #a78bfa; + --ig-tertiary-color: #34d399; + --ig-background-color: #0f172a; + --ig-card-bg-color: #1e293b; + --ig-text-color: #f8fafc; + --ig-muted-text-color: #94a3b8; + --ig-border-color: #334155; + --ig-header-gradient: linear-gradient(135deg, #4338ca, #6d28d9); +} .infographic-container-wrapper { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; line-height: 1.6; color: var(--ig-text-color); + background-color: var(--ig-background-color); height: 100%; display: flex; flex-direction: column; @@ -387,7 +899,7 @@ CSS_TEMPLATE_INFOGRAPHIC = """ .infographic-container-wrapper .user-context { font-size: 0.8em; color: var(--ig-muted-text-color); - background-color: #f1f5f9; + background-color: var(--ig-card-bg-color); padding: 8px 16px; display: flex; justify-content: space-around; @@ -402,7 +914,7 @@ CSS_TEMPLATE_INFOGRAPHIC = """ border-radius: 8px; padding: 16px; min-height: 600px; - background: #fff; + background: var(--ig-card-bg-color); overflow: visible; transition: height 0.3s ease; } @@ -414,6 +926,12 @@ CSS_TEMPLATE_INFOGRAPHIC = """ line-height: 1.3 !important; overflow: visible !important; } +.infographic-container-wrapper.dark .infographic-render-container svg text { + fill: var(--ig-text-color) !important; +} +.infographic-container-wrapper.dark .infographic-render-container svg foreignObject * { + color: var(--ig-text-color) !important; +} /* Main title styles */ .infographic-render-container svg foreignObject[data-element-type="title"] > * { font-size: 1.3em !important; @@ -640,6 +1158,7 @@ SCRIPT_TEMPLATE_INFOGRAPHIC = """ // Charts 'chart-column': 'chart-column-simple', 'quadrant': 'quadrant-quarter-simple-card', + 'relation-dagre': 'relation-dagre-flow-tb-simple-circle-node', // Legacy mappings for backward compatibility 'list-vertical': 'list-column-simple-vertical-arrow', @@ -701,6 +1220,24 @@ SCRIPT_TEMPLATE_INFOGRAPHIC = """ return; }} + // --- Auto Theme Loading --- + try {{ + const html = document.documentElement; + const body = document.body; + const htmlClass = html ? html.className : ''; + const bodyClass = body ? body.className : ''; + const htmlDataTheme = html ? html.getAttribute('data-theme') : ''; + + const wrapper = containerEl.closest('.infographic-container-wrapper'); + if (wrapper) {{ + if (htmlDataTheme === 'dark' || bodyClass.includes('dark') || htmlClass.includes('dark')) {{ + wrapper.classList.add('dark'); + }} + }} + }} catch (e) {{ + console.warn('[Infographic] Failed to apply theme class', e); + }} + try {{ const {{ Infographic }} = AntVInfographic; @@ -961,13 +1498,24 @@ class Action: def __init__(self): self.valves = self.Valves() + # Fallback mapping for variants not in TRANSLATIONS keys + self.fallback_map = { + "es-AR": "es-ES", + "es-MX": "es-ES", + "fr-CA": "fr-FR", + "en-CA": "en-US", + "en-GB": "en-US", + "en-AU": "en-US", + "de-AT": "de-DE", + } async def _get_user_context( self, __user__: Optional[Dict[str, Any]], __event_call__: Optional[Callable[[Any], Awaitable[None]]] = None, + __request__: Optional[Request] = None, ) -> Dict[str, str]: - """Safely extracts user context information.""" + """Extract basic user context with safe fallbacks.""" if isinstance(__user__, (list, tuple)): user_data = __user__[0] if __user__ else {} elif isinstance(__user__, dict): @@ -977,32 +1525,123 @@ class Action: user_id = user_data.get("id", "unknown_user") user_name = user_data.get("name", "User") + # Default from profile user_language = user_data.get("language", "en-US") + user_theme = "light" + # Level 1 Fallback: Accept-Language from __request__ headers + if ( + __request__ + and hasattr(__request__, "headers") + and "accept-language" in __request__.headers + ): + raw_lang = __request__.headers.get("accept-language", "") + if raw_lang: + user_language = raw_lang.split(",")[0].split(";")[0] + + # Priority: Document Lang > LocalStorage (Frontend) > Browser > Request Header > Profile if __event_call__: try: js_code = """ - return ( - localStorage.getItem('locale') || - localStorage.getItem('language') || - navigator.language || - 'en-US' - ); + try { + const html = document.documentElement; + const body = document.body; + const htmlClass = html ? html.className : ''; + const bodyClass = body ? body.className : ''; + const htmlDataTheme = html ? html.getAttribute('data-theme') : ''; + + let theme = 'light'; + + // 1. Check parent document's html/body class or data-theme + if (htmlDataTheme === 'dark' || bodyClass.includes('dark') || htmlClass.includes('dark')) { + theme = 'dark'; + } else if (htmlDataTheme === 'light' || bodyClass.includes('light') || htmlClass.includes('light')) { + theme = 'light'; + } else { + // 2. Check meta theme-color luma + const metas = document.querySelectorAll('meta[name="theme-color"]'); + let foundMeta = false; + if (metas.length > 0) { + const color = metas[metas.length - 1].content.trim(); + const m = color.match(/^#?([0-9a-f]{6})$/i); + if (m) { + const hex = m[1]; + const r = parseInt(hex.slice(0, 2), 16); + const g = parseInt(hex.slice(2, 4), 16); + const b = parseInt(hex.slice(4, 6), 16); + const luma = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255; + theme = luma < 0.5 ? 'dark' : 'light'; + foundMeta = true; + } + } + // 3. Check system preference + if (!foundMeta && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + theme = 'dark'; + } + } + + const lang = document.documentElement.lang || + localStorage.getItem('locale') || + localStorage.getItem('language') || + navigator.language || + 'en-US'; + + return JSON.stringify({ lang, theme }); + } catch (e) { + return JSON.stringify({ lang: 'en-US', theme: 'light' }); + } """ - frontend_lang = await __event_call__( - {"type": "execute", "data": {"code": js_code}} + # Use asyncio.wait_for to prevent hanging if frontend fails to callback + frontend_res_str = await asyncio.wait_for( + __event_call__({"type": "execute", "data": {"code": js_code}}), + timeout=2.0, ) - if frontend_lang and isinstance(frontend_lang, str): - user_language = frontend_lang + if frontend_res_str and isinstance(frontend_res_str, str): + try: + import json + frontend_res = json.loads(frontend_res_str) + user_language = frontend_res.get("lang", user_language) + user_theme = frontend_res.get("theme", user_theme) + except Exception: + user_language = frontend_res_str except Exception as e: - logger.warning(f"Failed to retrieve frontend language: {e}") + logger.warning(f"Failed to retrieve frontend language/theme: {e}") return { "user_id": user_id, "user_name": user_name, "user_language": user_language, + "user_theme": user_theme, } + def _resolve_language(self, lang: str) -> str: + """Resolve the best matching language code from the TRANSLATIONS dict.""" + target_lang = lang + if target_lang in TRANSLATIONS: + return target_lang + if hasattr(self, 'fallback_map') and target_lang in self.fallback_map: + target_lang = self.fallback_map[target_lang] + if target_lang in TRANSLATIONS: + return target_lang + if "-" in lang: + base_lang = lang.split("-")[0] + for supported_lang in TRANSLATIONS: + if supported_lang.startswith(base_lang + "-"): + return supported_lang + return "en-US" + + def _get_translation(self, lang: str, key: str, **kwargs) -> str: + """Get translated string for the given language and key.""" + target_lang = self._resolve_language(lang) + lang_dict = TRANSLATIONS.get(target_lang, TRANSLATIONS["en-US"]) + text = lang_dict.get(key, TRANSLATIONS["en-US"].get(key, key)) + if kwargs: + try: + text = text.format(**kwargs) + except Exception as e: + logger.warning(f"Translation formatting failed for {key}: {e}") + return text + def _get_chat_context( self, body: dict, __metadata__: Optional[dict] = None ) -> Dict[str, str]: @@ -1245,6 +1884,7 @@ class Action: 'sequence-horizontal': 'sequence-horizontal-zigzag-simple', 'relation-sankey': 'relation-sankey-simple', 'relation-circle': 'relation-circle-icon-badge', + 'relation-dagre': 'relation-dagre-flow-tb-simple-circle-node', 'compare-binary': 'compare-binary-horizontal-simple-vs', 'compare-swot': 'compare-swot', 'quadrant-quarter': 'quadrant-quarter-simple-card', @@ -1496,13 +2136,14 @@ class Action: __metadata__: Optional[dict] = None, __request__: Optional[Request] = None, ) -> Optional[dict]: - logger.info("Action: Infographic started (v1.4.0)") + logger.info("Action: Infographic started (v1.6.0)") # Get user information - user_ctx = await self._get_user_context(__user__, __event_call__) + user_ctx = await self._get_user_context(__user__, __event_call__, __request__) user_name = user_ctx["user_name"] user_id = user_ctx["user_id"] user_language = user_ctx["user_language"] + user_theme = user_ctx.get("user_theme", "light") # Get current time now = datetime.now() @@ -1562,11 +2203,11 @@ class Action: } await self._emit_notification( - __event_emitter__, "📊 Infographic started, generating...", "info" + __event_emitter__, self._get_translation(user_language, "status_starting"), "info" ) await self._emit_status( __event_emitter__, - "📊 Infographic: Starting generation...", + self._get_translation(user_language, "status_starting"), False, ) @@ -1576,13 +2217,14 @@ class Action: # Build prompt await self._emit_status( __event_emitter__, - "📊 Infographic: Calling AI model to analyze content...", + self._get_translation(user_language, "status_analyzing"), False, ) formatted_user_prompt = USER_PROMPT_GENERATE_INFOGRAPHIC.format( user_name=user_name, current_date_time_str=current_date_time_str, user_language=user_language, + user_theme=user_theme, long_text_content=long_text_content, ) @@ -1617,7 +2259,7 @@ class Action: await self._emit_status( __event_emitter__, - "📊 Infographic: AI analysis complete, parsing syntax...", + self._get_translation(user_language, "status_analyzing"), False, ) @@ -1631,7 +2273,7 @@ class Action: # Prepare content components await self._emit_status( __event_emitter__, - "📊 Infographic: Rendering chart...", + self._get_translation(user_language, "status_rendering_image"), False, ) content_html = ( @@ -1692,7 +2334,7 @@ class Action: await self._emit_status( __event_emitter__, - "📊 Infographic: Rendering image...", + self._get_translation(user_language, "status_rendering_image"), False, ) @@ -1712,11 +2354,11 @@ class Action: ) await self._emit_status( - __event_emitter__, "✅ Infographic: Image generated!", True + __event_emitter__, self._get_translation(user_language, "status_image_generated"), True ) await self._emit_notification( __event_emitter__, - f"📊 Infographic image generated, {user_name}!", + self._get_translation(user_language, "notification_image_success", user_name=user_name), "success", ) logger.info("Infographic generation completed in image mode") @@ -1727,11 +2369,11 @@ class Action: body["messages"][-1]["content"] = f"{original_content}\n\n{html_embed_tag}" await self._emit_status( - __event_emitter__, "✅ Infographic: Generation complete!", True + __event_emitter__, self._get_translation(user_language, "status_drawing"), True ) await self._emit_notification( __event_emitter__, - f"📊 Infographic generated, {user_name}!", + self._get_translation(user_language, "notification_success", user_name=user_name), "success", ) logger.info("Infographic generation completed") @@ -1745,11 +2387,11 @@ class Action: ] = f"{original_content}\n\n❌ **Error:** {user_facing_error}" await self._emit_status( - __event_emitter__, "❌ Infographic: Generation failed", True + __event_emitter__, self._get_translation(user_language, "status_failed"), True ) await self._emit_notification( __event_emitter__, - f"❌ Infographic generation failed, {user_name}!", + self._get_translation(user_language, "notification_failed", user_name=user_name), "error", ) diff --git a/plugins/actions/infographic/infographic_cn.png b/plugins/actions/infographic/infographic_cn.png deleted file mode 100644 index 65204d1..0000000 Binary files a/plugins/actions/infographic/infographic_cn.png and /dev/null differ diff --git a/plugins/actions/infographic/infographic_cn.py b/plugins/actions/infographic/infographic_cn.py deleted file mode 100644 index 7fb22c1..0000000 --- a/plugins/actions/infographic/infographic_cn.py +++ /dev/null @@ -1,1782 +0,0 @@ -""" -title: 智能信息图 -author: Fu-Jie -author_url: https://github.com/Fu-Jie/openwebui-extensions -funding_url: https://github.com/open-webui -icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxsaW5lIHgxPSIxMiIgeTE9IjIwIiB4Mj0iMTIiIHkyPSIxMCIgLz4KICA8bGluZSB4MT0iMTgiIHkxPSIyMCIgeDI9IjE4IiB5Mj0iNCIgLz4KICA8bGluZSB4MT0iNiIgeTE9IjIwIiB4Mj0iNiIgeTI9IjE2IiAvPgo8L3N2Zz4= -version: 1.5.0 -openwebui_id: e04a48ff-23ee-4a41-8ea7-66c19524e7c8 -description: 基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。 -""" - -from pydantic import BaseModel, Field -from typing import Optional, Dict, Any, Callable, Awaitable -import logging -import time -import re -from fastapi import Request -from datetime import datetime - -from open_webui.utils.chat import generate_chat_completion -from open_webui.models.users import Users - -logging.basicConfig( - level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - -# ================================================================= -# LLM 提示词 -# ================================================================= - -SYSTEM_PROMPT_INFOGRAPHIC_ASSISTANT = """ -You are a professional infographic design expert who can analyze user-provided text content and convert it into AntV Infographic syntax format. - -## Important Language Rule (语言规则) -- **Priority Input Language (优先使用输入语言)**: You must generate the text content of the infographic in the **exact same language** as the user's input content. -- **Example**: If the user provides a summary in Chinese, the labels and descriptions in the infographic must be in Chinese. - -## Infographic Syntax Specification - -Infographic syntax is a Mermaid-like declarative syntax for describing infographic templates, data, and themes. - -### Syntax Rules -- Entry uses `infographic ` -- Key-value pairs are separated by spaces, **absolutely NO colons allowed** -- Use two spaces for indentation -- Object arrays use `-` with line breaks - -⚠️ **IMPORTANT WARNING: This is NOT YAML format!** -- ❌ Wrong: `children:` `items:` `data:` (with colons) -- ✅ Correct: `children` `items` `data` (without colons) - -### 模板库与选择指南 - -根据内容结构选择最合适的模板。 - -**模板选择指南 (官方):** -- 严格时序 (流程/步骤/趋势) → `sequence-*` 系列 - - 时间线 → `sequence-timeline-simple` - - 路线图 → `sequence-roadmap-vertical-simple` - - 折线步骤 → `sequence-horizontal-zigzag-underline-text` - - 蛇形步骤 → `sequence-snake-steps-compact-card` -- 列举要点 → `list-row-horizontal-icon-arrow` 或 `list-column-simple-vertical-arrow` -- 对比分析 (A vs B) → `compare-binary-horizontal-underline-text-vs` -- SWOT 分析 → `compare-swot` -- 层级结构 (树状图) → `hierarchy-tree-tech-style-capsule-item` -- 数据图表 → `chart-*` 系列 -- 象限分析 → `quadrant-quarter-simple-card` -- 网格列表 → `list-grid-candy-card-lite` -- 关系展示 → `relation-circle-icon-badge` - -**可用模板:** - -*Sequence (时序/流程):* -`sequence-timeline-simple`, `sequence-roadmap-vertical-simple`, `sequence-horizontal-zigzag-underline-text`, -`sequence-snake-steps-compact-card`, `sequence-zigzag-steps-underline-text`, `sequence-circular-simple` - -*List (列表):* -`list-grid-candy-card-lite`, `list-grid-badge-card`, `list-row-horizontal-icon-arrow`, -`list-column-simple-vertical-arrow`, `list-column-done-list` - -*Compare (对比):* -`compare-binary-horizontal-underline-text-vs`, `compare-swot` - -*Hierarchy (层级):* -`hierarchy-tree-tech-style-capsule-item`, `hierarchy-structure` - -*Chart (图表):* -`chart-column-simple`, `chart-bar-plain-text`, `chart-pie-plain-text`, `chart-wordcloud` - -*Other:* -`quadrant-quarter-simple-card`, `relation-circle-icon-badge` - -**按容量分类:** -- 高容量 (长描述): `list-column-*`, `compare-binary-*`, `sequence-timeline-*` -- 中容量: `list-row-*`, `sequence-roadmap-*` -- 低容量 (短文本): `list-grid-*`, `hierarchy-*` - -### 图标和插图资源 - -**图标 (Iconify):** -- 格式: `<集合>/<图标名>`, 如 `mdi/rocket-launch` -- 常用: `mdi/*`, `fa/*`, `bi/*` - -**插图 (unDraw):** -- 格式: 文件名 (不含 .svg), 如 `coding`, `team-work` -- 使用 `illus` 字段 - -### Infographic Syntax Guide - -#### 1. Structure -- **Entry**: `infographic ` -- **Blocks**: `data`, `theme`, `design` (optional) -- **Format**: Key-value pairs separated by spaces, 2-space indentation. -- **Arrays**: Object arrays use `-` (newline), simple arrays use inline values. - -#### 2. Data Block (`data`) -- `title`: Main title -- `desc`: Subtitle or description -- `items`: List of data items -- - `label`: Item title -- - `value`: Numerical value (required for Charts/Stats) -- - `desc`: Item description (optional) -- - `icon`: Icon name (e.g., `mdi/rocket-launch`) -- - `time`: Time label (Optional, for Roadmap/Sequence) -- - `children`: Nested items (ONLY for Tree/Mindmap/Sankey/SWOT) -- - `illus`: Illustration name (ONLY for Quadrant) - -#### 3. Theme Block (`theme`) -- `colorPrimary`: Main color (Hex) -- `colorBg`: Background color (Hex) -- `palette`: Color list (Space separated) -- `textColor`: Text color (Hex) -- `stylize`: Style effect configuration -- `type`: Style type (`rough`, `pattern`, `linear-gradient`, `radial-gradient`) - -#### 4. Stylize Examples -**Rough Style (Hand-drawn):** -```infographic -infographic list-row-simple-horizontal-arrow -theme - stylize rough -data - ... -``` - -**Gradient Style:** -```infographic -infographic chart-bar -theme - stylize linear-gradient -data - ... -``` - -### Examples - -#### Chart (Bar Chart) -infographic chart-bar -data - title Revenue Growth - desc Monthly revenue in 2024 - items - - label Jan - value 1200 - - label Feb - value 1500 - - label Mar - value 1800 - - -#### Comparison (Binary Comparison) -infographic compare-binary -data - title Advantages vs Disadvantages - desc Compare two aspects side by side - items - - label Advantages - children - - label Strong R&D - desc Leading technology and innovation capability - - label High customer loyalty - desc Repurchase rate over 60% - - label Disadvantages - children - - label Weak brand exposure - desc Insufficient marketing, low awareness - - label Narrow channel coverage - desc Limited online channels - -#### Comparison (SWOT) -infographic compare-swot -data - title Project SWOT - items - - label Strengths - children - - label Strong team - - label Innovative tech - - label Weaknesses - children - - label Limited budget - - label Opportunities - children - - label Emerging market - - label Threats - children - - label High competition - -#### Relationship (Sankey) -infographic relation-sankey -data - title Energy Flow - items - - label Solar - value 100 - children - - label Grid - value 60 - - label Battery - value 40 - - label Wind - value 80 - children - - label Grid - value 80 - -#### Quadrant (Importance vs Urgency) -infographic quadrant-quarter -data - title Task Management - items - - label Critical Bug - desc Fix immediately - illus mdi/bug - - label Feature Request - desc Plan for next sprint - illus mdi/star - -### Content Refinement Principles -1. **Brevity is King**: Infographics are visual. Keep text to a minimum. -2. **Title Limit**: Keep `label` (item titles) under 15 characters. -3. **Description Limit**: Keep `desc` (item descriptions) under 25 characters (approx. 2 lines). -4. **Impact**: Use strong verbs and nouns. Avoid filler words. - -### Output Rules -1. **Strict Syntax**: Follow the indentation and formatting rules exactly. -2. **No Explanations**: Output ONLY the syntax code block. -3. **Language**: Use the user's requested language for content. -""" - -import json - -USER_PROMPT_GENERATE_INFOGRAPHIC = """ -请分析以下文本内容,将其核心信息转换为 AntV Infographic 语法格式。 - ---- -**用户上下文信息:** -用户姓名: {user_name} -当前日期时间: {current_date_time_str} -用户语言: {user_language} ---- - -**文本内容:** -{long_text_content} - -请根据文本特点选择最合适的信息图模板,并输出规范的 infographic 语法。注意保持正确的缩进格式(两个空格)。 - -**视觉优化指南:** -- **要点化生成:** 信息图不是文章。请将内容转化为“关键词+短语”的形式,严禁生成长难句。 -- **标题限制:** 每个卡片的 `label`(标题)请控制在 **8个汉字**以内。 -- **描述限制:** 每个卡片的 `desc`(描述)请控制在 **15个汉字**以内,确保即使在小屏幕上也能完整显示。 -- **结构化思维:** 优先使用并列、递进或对比结构,使信息一目了然。 -""" - -# ================================================================= -# HTML 容器模板 -# ================================================================= - -HTML_WRAPPER_TEMPLATE = """ - - - - - - - - - -
- -
- - - -""" - -# ================================================================= -# CSS 样式模板 -# ================================================================= - -CSS_TEMPLATE_INFOGRAPHIC = """ -:root { - --ig-primary-color: #6366f1; - --ig-secondary-color: #8b5cf6; - --ig-tertiary-color: #10b981; - --ig-background-color: #f8fafc; - --ig-card-bg-color: #ffffff; - --ig-text-color: #1e293b; - --ig-muted-text-color: #64748b; - --ig-border-color: #e2e8f0; - --ig-header-gradient: linear-gradient(135deg, #6366f1, #8b5cf6); -} -.infographic-container-wrapper { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - line-height: 1.6; - color: var(--ig-text-color); - height: 100%; - display: flex; - flex-direction: column; -} -.infographic-container-wrapper .header { - background: var(--ig-header-gradient); - color: white; - padding: 20px 24px; - text-align: center; -} -.infographic-container-wrapper .header h1 { - margin: 0; - font-size: 1.5em; - font-weight: 600; -} -.infographic-container-wrapper .user-context { - font-size: 0.8em; - color: var(--ig-muted-text-color); - background-color: #f1f5f9; - padding: 8px 16px; - display: flex; - justify-content: space-around; - flex-wrap: wrap; - border-bottom: 1px solid var(--ig-border-color); -} -.infographic-container-wrapper .content-area { - padding: 20px; - flex-grow: 1; -} -.infographic-container-wrapper .infographic-render-container { - border-radius: 8px; - padding: 16px; - min-height: 600px; - background: #fff; - overflow: visible; - transition: height 0.3s ease; -} -.infographic-render-container svg text { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif !important; -} -.infographic-render-container svg foreignObject { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif !important; - line-height: 1.3 !important; - overflow: visible !important; -} -/* 主标题样式 */ -.infographic-render-container svg foreignObject[data-element-type="title"] > * { - font-size: 1.3em !important; - font-weight: 800 !important; - line-height: 1.3 !important; - white-space: normal !important; - word-break: break-word !important; - display: -webkit-box !important; - -webkit-line-clamp: 2 !important; - -webkit-box-orient: vertical !important; - overflow: hidden !important; - text-overflow: ellipsis !important; - text-align: center !important; -} -/* 页面副标题样式 */ -.infographic-render-container svg foreignObject[data-element-type="desc"] > * { - font-size: 0.85em !important; - line-height: 1.3 !important; - white-space: nowrap !important; - overflow: hidden !important; - text-overflow: ellipsis !important; - text-align: center !important; - display: block !important; - color: var(--ig-muted-text-color) !important; -} -/* 卡片标题样式 */ -.infographic-render-container svg foreignObject[data-element-type="item-label"] > * { - font-size: 0.9em !important; - font-weight: 600 !important; - line-height: 1.3 !important; - white-space: normal !important; - word-break: break-word !important; - display: -webkit-box !important; - -webkit-line-clamp: 2 !important; - -webkit-box-orient: vertical !important; - overflow: hidden !important; - text-overflow: ellipsis !important; - padding-bottom: 2px !important; -} -/* 卡片描述文字 */ -.infographic-render-container svg foreignObject[data-element-type="item-desc"] > * { - font-size: 0.82em !important; - line-height: 1.3 !important; - white-space: normal !important; - display: -webkit-box !important; - -webkit-line-clamp: 2 !important; - -webkit-box-orient: vertical !important; - overflow: hidden !important; - text-overflow: ellipsis !important; -} -.infographic-container-wrapper .download-area { - text-align: center; - padding-top: 20px; - margin-top: 20px; - border-top: 1px solid var(--ig-border-color); -} -.infographic-container-wrapper .download-btn { - background-color: var(--ig-primary-color); - color: white; - border: none; - padding: 8px 16px; - border-radius: 6px; - font-size: 0.9em; - cursor: pointer; - transition: all 0.2s; - margin: 4px 6px; - display: inline-flex; - align-items: center; - gap: 6px; -} -.infographic-container-wrapper .download-btn.secondary { - background-color: var(--ig-secondary-color); -} -.infographic-container-wrapper .download-btn.tertiary { - background-color: var(--ig-tertiary-color); -} -.infographic-container-wrapper .download-btn:hover { - transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(0,0,0,0.1); -} -.infographic-container-wrapper .footer { - text-align: center; - padding: 16px; - font-size: 0.8em; - color: var(--ig-muted-text-color); - background-color: #f8fafc; - border-top: 1px solid var(--ig-border-color); -} -.infographic-container-wrapper .error-message { - color: #dc2626; - background-color: #fef2f2; - border: 1px solid #fecaca; - padding: 16px; - border-radius: 8px; - text-align: center; -} -""" - -# ================================================================= -# HTML 内容模板 -# ================================================================= - -CONTENT_TEMPLATE_INFOGRAPHIC = """ -
-
-

📊 智能信息图

-
-
- 用户: {user_name} - 时间: {current_date_time_str} -
-
-
-
- - - -
-
- -
- - -""" - -# ================================================================= -# JavaScript 渲染脚本 -# ================================================================= - -SCRIPT_TEMPLATE_INFOGRAPHIC = """ - - -""" - - -class Action: - class Valves(BaseModel): - SHOW_STATUS: bool = Field( - default=True, description="是否在聊天界面显示操作状态更新。" - ) - MODEL_ID: str = Field( - default="", - description="用于文本分析的内置LLM模型ID。如果为空,则使用当前对话的模型。", - ) - MIN_TEXT_LENGTH: int = Field( - default=100, - description="进行信息图分析所需的最小文本长度(字符数)。", - ) - CLEAR_PREVIOUS_HTML: bool = Field( - default=False, - description="是否强制清除旧的插件结果(如果为 True,则不合并,直接覆盖)。", - ) - MESSAGE_COUNT: int = Field( - default=1, - description="用于生成的最近消息数量。设置为1仅使用最后一条消息,更大值可包含更多上下文。", - ) - OUTPUT_MODE: str = Field( - default="image", - description="输出模式:'html' 为交互式HTML,'image' 将嵌入为Markdown图片(默认)。", - ) - SHOW_DEBUG_LOG: bool = Field( - default=False, - description="是否在浏览器控制台打印调试日志。", - ) - - def __init__(self): - self.valves = self.Valves() - self.weekday_map = { - "Monday": "星期一", - "Tuesday": "星期二", - "Wednesday": "星期三", - "Thursday": "星期四", - "Friday": "星期五", - "Saturday": "星期六", - "Sunday": "星期日", - } - - async def _get_user_context( - self, - __user__: Optional[Dict[str, Any]], - __event_call__: Optional[Callable[[Any], Awaitable[None]]] = None, - ) -> Dict[str, str]: - """安全提取用户上下文信息。""" - if isinstance(__user__, (list, tuple)): - user_data = __user__[0] if __user__ else {} - elif isinstance(__user__, dict): - user_data = __user__ - else: - user_data = {} - - user_id = user_data.get("id", "unknown_user") - user_name = user_data.get("name", "用户") - user_language = user_data.get("language", "zh-CN") - - if __event_call__: - try: - js_code = """ - return ( - localStorage.getItem('locale') || - localStorage.getItem('language') || - navigator.language || - 'zh-CN' - ); - """ - frontend_lang = await __event_call__( - {"type": "execute", "data": {"code": js_code}} - ) - if frontend_lang and isinstance(frontend_lang, str): - user_language = frontend_lang - except Exception as e: - pass - - return { - "user_id": user_id, - "user_name": user_name, - "user_language": user_language, - } - - def _get_chat_context( - self, body: dict, __metadata__: Optional[dict] = None - ) -> Dict[str, str]: - """ - 统一提取聊天上下文信息 (chat_id, message_id)。 - 优先从 body 中提取,其次从 metadata 中提取。 - """ - chat_id = "" - message_id = "" - - # 1. 尝试从 body 获取 - if isinstance(body, dict): - chat_id = body.get("chat_id", "") - message_id = body.get("id", "") # message_id 在 body 中通常是 id - - # 再次检查 body.metadata - if not chat_id or not message_id: - body_metadata = body.get("metadata", {}) - if isinstance(body_metadata, dict): - if not chat_id: - chat_id = body_metadata.get("chat_id", "") - if not message_id: - message_id = body_metadata.get("message_id", "") - - # 2. 尝试从 __metadata__ 获取 (作为补充) - if __metadata__ and isinstance(__metadata__, dict): - if not chat_id: - chat_id = __metadata__.get("chat_id", "") - if not message_id: - message_id = __metadata__.get("message_id", "") - - return { - "chat_id": str(chat_id).strip(), - "message_id": str(message_id).strip(), - } - - def _extract_infographic_syntax(self, llm_output: str) -> str: - """提取LLM输出中的infographic语法""" - # 1. 优先匹配 ```infographic - match = re.search(r"```infographic\s*(.*?)\s*```", llm_output, re.DOTALL) - if match: - return match.group(1).strip().replace("", "<\\/script>") - - # 2. 其次匹配 ```mermaid (有时 LLM 会混淆) - match = re.search(r"```mermaid\s*(.*?)\s*```", llm_output, re.DOTALL) - if match: - content = match.group(1).strip() - # 简单检查是否包含 infographic 关键字 - if "infographic" in content or "data" in content: - return content.replace("", "<\\/script>") - - # 3. 再次匹配通用 ``` (无语言标记) - match = re.search(r"```\s*(.*?)\s*```", llm_output, re.DOTALL) - if match: - content = match.group(1).strip() - # 简单的启发式检查 - if "infographic" in content or "data" in content: - return content.replace("", "<\\/script>") - - # 4. 兜底:如果看起来像直接输出了语法(以 infographic 或 list-grid 等开头) - cleaned_output = llm_output.strip() - first_line = cleaned_output.split("\n")[0].lower() - if ( - first_line.startswith("infographic") - or first_line.startswith("list-") - or first_line.startswith("tree-") - or first_line.startswith("mindmap") - ): - return cleaned_output.replace("", "<\\/script>") - - logger.warning("LLM输出未严格遵循预期格式,将整个输出作为语法处理。") - return cleaned_output.replace("", "<\\/script>") - - async def _emit_status(self, emitter, description: str, done: bool = False): - """发送状态更新事件""" - if self.valves.SHOW_STATUS and emitter: - await emitter( - {"type": "status", "data": {"description": description, "done": done}} - ) - - async def _emit_notification(self, emitter, content: str, ntype: str = "info"): - """发送通知事件 (info/success/warning/error)""" - if emitter: - await emitter( - {"type": "notification", "data": {"type": ntype, "content": content}} - ) - - async def _emit_debug_log(self, emitter, title: str, data: dict): - """在浏览器控制台打印结构化调试日志""" - if not self.valves.SHOW_DEBUG_LOG or not emitter: - return - - try: - js_code = f""" - (async function() {{ - console.group("🛠️ {title}"); - console.log({json.dumps(data, ensure_ascii=False)}); - console.groupEnd(); - }})(); - """ - - await emitter({"type": "execute", "data": {"code": js_code}}) - except Exception as e: - print(f"Error emitting debug log: {e}") - - def _remove_existing_html(self, content: str) -> str: - """移除内容中已有的插件生成 HTML 代码块""" - pattern = r"```html\s*[\s\S]*?```" - return re.sub(pattern, "", content).strip() - - def _extract_text_content(self, content) -> str: - """从消息内容中提取文本,支持多模态消息格式""" - if isinstance(content, str): - return content - elif isinstance(content, list): - # 多模态消息: [{"type": "text", "text": "..."}, {"type": "image_url", ...}] - text_parts = [] - for item in content: - if isinstance(item, dict) and item.get("type") == "text": - text_parts.append(item.get("text", "")) - elif isinstance(item, str): - text_parts.append(item) - return "\n".join(text_parts) - return str(content) if content else "" - - def _merge_html( - self, - existing_html_code: str, - new_content: str, - new_styles: str = "", - new_scripts: str = "", - user_language: str = "zh-CN", - ) -> str: - """将新内容合并到现有的 HTML 容器中,或者创建一个新的容器""" - if ( - "" in existing_html_code - and "" in existing_html_code - ): - base_html = existing_html_code - base_html = re.sub(r"^```html\s*", "", base_html) - base_html = re.sub(r"\s*```$", "", base_html) - else: - base_html = HTML_WRAPPER_TEMPLATE.replace("{user_language}", user_language) - - wrapped_content = f'
\n{new_content}\n
' - - if new_styles: - base_html = base_html.replace( - "/* STYLES_INSERTION_POINT */", - f"{new_styles}\n/* STYLES_INSERTION_POINT */", - ) - - base_html = base_html.replace( - "", - f"{wrapped_content}\n", - ) - - if new_scripts: - base_html = base_html.replace( - "", - f"{new_scripts}\n", - ) - - return base_html.strip() - - def _generate_image_js_code( - self, - unique_id: str, - chat_id: str, - message_id: str, - infographic_syntax: str, - ) -> str: - """生成前端 SVG 渲染和图片嵌入的 JavaScript 代码""" - - # 转义语法以便在 JS 中嵌入 - syntax_escaped = ( - infographic_syntax.replace("\\", "\\\\") - .replace("`", "\\`") - .replace("${", "\\${") - .replace("", "<\\/script>") - ) - - return f""" -(async function() {{ - const uniqueId = "{unique_id}"; - const chatId = "{chat_id}"; - const messageId = "{message_id}"; - const defaultWidth = 1100; - const defaultHeight = 500; - - // 自动检测聊天容器宽度以实现响应式尺寸 - let svgWidth = defaultWidth; - let svgHeight = defaultHeight; - const chatContainer = document.getElementById('chat-container'); - if (chatContainer) {{ - const containerWidth = chatContainer.clientWidth; - if (containerWidth > 100) {{ - // 使用容器宽度的 80%(右边留更多空间) - svgWidth = Math.floor(containerWidth * 0.8); - // 根据默认尺寸保持宽高比 - svgHeight = Math.floor(svgWidth * (defaultHeight / defaultWidth)); - console.log("[Infographic Image] 自动检测容器宽度:", containerWidth, "-> SVG:", svgWidth, "x", svgHeight); - }} - }} - - console.log("[Infographic Image] 开始渲染..."); - console.log("[Infographic Image] chatId:", chatId, "messageId:", messageId); - - try {{ - // 加载 AntV Infographic(如果未加载) - if (typeof AntVInfographic === 'undefined') {{ - console.log("[Infographic Image] 加载 AntV Infographic..."); - await new Promise((resolve, reject) => {{ - const script = document.createElement('script'); - script.src = 'https://registry.npmmirror.com/@antv/infographic/0.2.1/files/dist/infographic.min.js'; - script.onload = resolve; - script.onerror = reject; - document.head.appendChild(script); - }}); - }} - - const {{ Infographic }} = AntVInfographic; - - // 获取语法内容 - let syntaxContent = `{syntax_escaped}`; - console.log("[Infographic Image] 语法长度:", syntaxContent.length); - - // 清理语法:移除代码块标记 - const backtick = String.fromCharCode(96); - const prefix = backtick + backtick + backtick + 'infographic'; - const simplePrefix = backtick + backtick + backtick; - - if (syntaxContent.toLowerCase().startsWith(prefix)) {{ - syntaxContent = syntaxContent.substring(prefix.length).trim(); - }} else if (syntaxContent.startsWith(simplePrefix)) {{ - syntaxContent = syntaxContent.substring(simplePrefix.length).trim(); - }} - - if (syntaxContent.endsWith(simplePrefix)) {{ - syntaxContent = syntaxContent.substring(0, syntaxContent.length - simplePrefix.length).trim(); - }} - - // 修复语法:移除关键字后的冒号 - syntaxContent = syntaxContent.replace(/^(data|items|children|theme|config):/gm, '$1'); - syntaxContent = syntaxContent.replace(/(\\s)(children|items):/g, '$1$2'); - - // 确保 infographic 前缀 - if (!syntaxContent.trim().toLowerCase().startsWith('infographic')) {{ - const firstWord = syntaxContent.trim().split(/\\s+/)[0].toLowerCase(); - if (!['data', 'theme', 'design', 'items'].includes(firstWord)) {{ - syntaxContent = 'infographic ' + syntaxContent; - }} - }} - - // 模板映射 - const TEMPLATE_MAPPING = {{ - 'list-grid': 'list-grid-compact-card', - 'list-vertical': 'list-column-simple-vertical-arrow', - 'tree-vertical': 'hierarchy-tree-tech-style-capsule-item', - 'tree-horizontal': 'hierarchy-tree-lr-tech-style-capsule-item', - 'mindmap': 'hierarchy-mindmap-branch-gradient-capsule-item', - 'sequence-roadmap': 'sequence-roadmap-vertical-simple', - 'sequence-zigzag': 'sequence-horizontal-zigzag-simple', - 'sequence-horizontal': 'sequence-horizontal-zigzag-simple', - 'relation-sankey': 'relation-sankey-simple', - 'relation-circle': 'relation-circle-icon-badge', - 'compare-binary': 'compare-binary-horizontal-simple-vs', - 'compare-swot': 'compare-swot', - 'quadrant-quarter': 'quadrant-quarter-simple-card', - 'statistic-card': 'list-grid-compact-card', - 'chart-bar': 'chart-bar-plain-text', - 'chart-column': 'chart-column-simple', - 'chart-line': 'chart-line-plain-text', - 'chart-area': 'chart-area-simple', - 'chart-pie': 'chart-pie-plain-text', - 'chart-doughnut': 'chart-pie-donut-plain-text' - }}; - - for (const [key, value] of Object.entries(TEMPLATE_MAPPING)) {{ - const regex = new RegExp(`infographic\\\\s+${{key}}(?=\\\\s|$)`, 'i'); - if (regex.test(syntaxContent)) {{ - syntaxContent = syntaxContent.replace(regex, `infographic ${{value}}`); - break; - }} - }} - - // 创建离屏容器 - const container = document.createElement('div'); - container.id = 'infographic-offscreen-' + uniqueId; - container.style.cssText = 'position:absolute;left:-9999px;top:-9999px;width:' + svgWidth + 'px;height:' + svgHeight + 'px;background:#ffffff;'; - document.body.appendChild(container); - - // 创建信息图实例 - const instance = new Infographic({{ - container: '#' + container.id, - width: svgWidth, - height: svgHeight, - padding: 12, - }}); - - console.log("[Infographic Image] 渲染信息图..."); - instance.render(syntaxContent); - - // 等待渲染完成 - await new Promise(resolve => setTimeout(resolve, 2000)); - - // 获取 SVG 元素 - const svgEl = container.querySelector('svg'); - if (!svgEl) {{ - throw new Error('渲染后未找到 SVG 元素'); - }} - - // 获取实际尺寸 - const bbox = svgEl.getBoundingClientRect(); - const width = bbox.width || svgWidth; - const height = bbox.height || svgHeight; - - // 克隆并准备导出的 SVG - const clonedSvg = svgEl.cloneNode(true); - clonedSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); - clonedSvg.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); - clonedSvg.setAttribute('width', width); - clonedSvg.setAttribute('height', height); - - // 添加背景矩形 - const bgRect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); - bgRect.setAttribute('width', '100%'); - bgRect.setAttribute('height', '100%'); - bgRect.setAttribute('fill', '#ffffff'); - clonedSvg.insertBefore(bgRect, clonedSvg.firstChild); - - // 序列化 SVG 为字符串 - const svgData = new XMLSerializer().serializeToString(clonedSvg); - - // 清理容器 - document.body.removeChild(container); - - // 使用 canvas 将 SVG 转换为 PNG 以提高兼容性 - console.log("[Infographic Image] 正在将 SVG 转换为 PNG..."); - const pngBlob = await new Promise((resolve, reject) => {{ - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - const scale = 2; // 更高分辨率以提高清晰度 - canvas.width = Math.round(width * scale); - canvas.height = Math.round(height * scale); - - // 填充白色背景 - ctx.fillStyle = '#ffffff'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.scale(scale, scale); - - const img = new Image(); - img.onload = () => {{ - ctx.drawImage(img, 0, 0, width, height); - canvas.toBlob((blob) => {{ - if (blob) {{ - resolve(blob); - }} else {{ - reject(new Error('Canvas toBlob 失败')); - }} - }}, 'image/png'); - }}; - img.onerror = (e) => reject(new Error('加载 SVG 图片失败: ' + e)); - img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData))); - }}); - - const file = new File([pngBlob], `infographic-${{uniqueId}}.png`, {{ type: 'image/png' }}); - - // 上传文件到 OpenWebUI API - console.log("[Infographic Image] 上传 PNG 文件..."); - const token = localStorage.getItem("token"); - const formData = new FormData(); - formData.append('file', file); - - const uploadResponse = await fetch('/api/v1/files/', {{ - method: 'POST', - headers: {{ - 'Authorization': `Bearer ${{token}}` - }}, - body: formData - }}); - - if (!uploadResponse.ok) {{ - throw new Error(`上传失败: ${{uploadResponse.statusText}}`); - }} - - const fileData = await uploadResponse.json(); - const fileId = fileData.id; - const imageUrl = `/api/v1/files/${{fileId}}/content`; - - console.log("[Infographic Image] PNG 文件已上传, ID:", fileId); - - // 生成带文件 URL 的 markdown 图片 - const markdownImage = `![📊 信息图](${{imageUrl}})`; - - // 通过 API 更新消息 - if (chatId && messageId) {{ - - // 带重试逻辑的辅助函数 - const fetchWithRetry = async (url, options, retries = 3) => {{ - for (let i = 0; i < retries; i++) {{ - try {{ - const response = await fetch(url, options); - if (response.ok) return response; - if (i < retries - 1) {{ - console.log(`[Infographic Image] 重试 ${{i + 1}}/${{retries}} for ${{url}}`); - await new Promise(r => setTimeout(r, 1000 * (i + 1))); - }} - }} catch (e) {{ - if (i === retries - 1) throw e; - await new Promise(r => setTimeout(r, 1000 * (i + 1))); - }} - }} - return null; - }}; - - // 获取当前聊天数据 - const getResponse = await fetch(`/api/v1/chats/${{chatId}}`, {{ - method: "GET", - headers: {{ "Authorization": `Bearer ${{token}}` }} - }}); - - if (!getResponse.ok) {{ - throw new Error("获取聊天数据失败: " + getResponse.status); - }} - - const chatData = await getResponse.json(); - let updatedMessages = []; - let newContent = ""; - - if (chatData.chat && chatData.chat.messages) {{ - updatedMessages = chatData.chat.messages.map(m => {{ - if (m.id === messageId) {{ - const originalContent = m.content || ""; - // 移除已有的信息图图片 - const infographicPattern = /\\n*!\\[📊[^\\]]*\\]\\((?:data:image\\/[^)]+|(?:\\/api\\/v1\\/files\\/[^)]+))\\)/g; - let cleanedContent = originalContent.replace(infographicPattern, ""); - cleanedContent = cleanedContent.replace(/\\n{{3,}}/g, "\\n\\n").trim(); - // 追加新图片 - newContent = cleanedContent + "\\n\\n" + markdownImage; - - // 同时更新 history 对象 - if (chatData.chat.history && chatData.chat.history.messages) {{ - if (chatData.chat.history.messages[messageId]) {{ - chatData.chat.history.messages[messageId].content = newContent; - }} - }} - - return {{ ...m, content: newContent }}; - }} - return m; - }}); - }} - - if (!newContent) {{ - console.warn("[Infographic Image] 找不到要更新的消息"); - return; - }} - - // 尝试通过事件 API 更新前端显示 - try {{ - await fetch(`/api/v1/chats/${{chatId}}/messages/${{messageId}}/event`, {{ - method: "POST", - headers: {{ - "Content-Type": "application/json", - "Authorization": `Bearer ${{token}}` - }}, - body: JSON.stringify({{ - type: "chat:message", - data: {{ content: newContent }} - }}) - }}); - }} catch (eventErr) {{ - console.log("[Infographic Image] 事件 API 不可用,继续..."); - }} - - // 持久化到数据库 - const updatePayload = {{ - chat: {{ - ...chatData.chat, - messages: updatedMessages - }} - }}; - - const persistResponse = await fetchWithRetry(`/api/v1/chats/${{chatId}}`, {{ - method: "POST", - headers: {{ - "Content-Type": "application/json", - "Authorization": `Bearer ${{token}}` - }}, - body: JSON.stringify(updatePayload) - }}); - - if (persistResponse && persistResponse.ok) {{ - console.log("[Infographic Image] ✅ 消息持久化成功!"); - }} else {{ - console.error("[Infographic Image] ❌ 重试后消息持久化失败"); - }} - }} else {{ - console.warn("[Infographic Image] ⚠️ 缺少 chatId 或 messageId,无法持久化"); - }} - - }} catch (error) {{ - console.error("[Infographic Image] 错误:", error); - }} -}})(); -""" - - async def action( - self, - body: dict, - __user__: Optional[Dict[str, Any]] = None, - __event_emitter__: Optional[Any] = None, - __event_call__: Optional[Callable[[Any], Awaitable[None]]] = None, - __metadata__: Optional[dict] = None, - __request__: Optional[Request] = None, - ) -> Optional[dict]: - logger.info("Action: 信息图启动 (v1.4.0)") - - # 获取用户信息 - user_ctx = await self._get_user_context(__user__, __event_call__) - user_name = user_ctx["user_name"] - user_id = user_ctx["user_id"] - user_language = user_ctx["user_language"] - - # 获取当前时间 - now = datetime.now() - current_date_time_str = now.strftime("%Y年%m月%d日 %H:%M:%S") - current_weekday_en = now.strftime("%A") - current_weekday = self.weekday_map.get(current_weekday_en, current_weekday_en) - current_year = now.strftime("%Y") - - original_content = "" - try: - messages = body.get("messages", []) - if not messages: - raise ValueError("无法获取有效的用户消息内容。") - - # 根据 MESSAGE_COUNT 获取最近 N 条消息 - message_count = min(self.valves.MESSAGE_COUNT, len(messages)) - recent_messages = messages[-message_count:] - - # 聚合选中消息的内容,带标签 - aggregated_parts = [] - for i, msg in enumerate(recent_messages, 1): - text_content = self._extract_text_content(msg.get("content")) - if text_content: - role = msg.get("role", "unknown") - role_label = ( - "用户" - if role == "user" - else "助手" if role == "assistant" else role - ) - aggregated_parts.append(f"{text_content}") - - if not aggregated_parts: - raise ValueError("无法获取有效的用户消息内容。") - - original_content = "\n\n---\n\n".join(aggregated_parts) - - # 提取非HTML部分的文本 - parts = re.split(r"```html.*?```", original_content, flags=re.DOTALL) - long_text_content = "" - if parts: - for part in reversed(parts): - if part.strip(): - long_text_content = part.strip() - break - - if not long_text_content: - long_text_content = original_content.strip() - - # 检查文本长度 - if len(long_text_content) < self.valves.MIN_TEXT_LENGTH: - short_text_message = f"文本内容过短({len(long_text_content)}字符),无法进行有效分析。请提供至少{self.valves.MIN_TEXT_LENGTH}字符的文本。" - await self._emit_notification( - __event_emitter__, short_text_message, "warning" - ) - return { - "messages": [ - {"role": "assistant", "content": f"⚠️ {short_text_message}"} - ] - } - - await self._emit_notification( - __event_emitter__, "📊 信息图已启动,正在生成...", "info" - ) - await self._emit_status(__event_emitter__, "📊 信息图: 开始生成...", False) - - # 生成唯一ID - unique_id = f"id_{int(time.time() * 1000)}" - - # 构建提示词 - await self._emit_status( - __event_emitter__, "📊 信息图: 正在调用 AI 模型分析内容...", False - ) - formatted_user_prompt = USER_PROMPT_GENERATE_INFOGRAPHIC.format( - user_name=user_name, - current_date_time_str=current_date_time_str, - user_language=user_language, - long_text_content=long_text_content, - ) - - # 确定使用的模型 - target_model = self.valves.MODEL_ID - if not target_model: - target_model = body.get("model") - - llm_payload = { - "model": target_model, - "messages": [ - {"role": "system", "content": SYSTEM_PROMPT_INFOGRAPHIC_ASSISTANT}, - {"role": "user", "content": formatted_user_prompt}, - ], - "stream": False, - } - - user_obj = Users.get_user_by_id(user_id) - if not user_obj: - raise ValueError(f"无法获取用户对象,用户ID: {user_id}") - - llm_response = await generate_chat_completion( - __request__, llm_payload, user_obj - ) - - if ( - not llm_response - or "choices" not in llm_response - or not llm_response["choices"] - ): - raise ValueError("无效的 LLM 响应格式或为空。") - - await self._emit_status( - __event_emitter__, "📊 信息图: AI 分析完成,正在解析语法...", False - ) - - assistant_response_content = llm_response["choices"][0]["message"][ - "content" - ] - infographic_syntax = self._extract_infographic_syntax( - assistant_response_content - ) - - # 准备内容组件 - await self._emit_status( - __event_emitter__, "📊 信息图: 正在渲染图表...", False - ) - content_html = ( - CONTENT_TEMPLATE_INFOGRAPHIC.replace("{unique_id}", unique_id) - .replace("{user_name}", user_name) - .replace("{current_date_time_str}", current_date_time_str) - .replace("{current_year}", current_year) - .replace("{infographic_syntax}", infographic_syntax) - ) - - # 先替换占位符,然后将 {{ 转为 { 和 }} 转为 } - script_html = SCRIPT_TEMPLATE_INFOGRAPHIC.replace("{unique_id}", unique_id) - script_html = script_html.replace("{{", "{").replace("}}", "}") - - # 提取现有HTML(如果有) - existing_html_block = "" - match = re.search( - r"```html\s*([\s\S]*?)```", - original_content, - ) - if match: - existing_html_block = match.group(1) - - if self.valves.CLEAR_PREVIOUS_HTML: - original_content = self._remove_existing_html(original_content) - final_html = self._merge_html( - "", - content_html, - CSS_TEMPLATE_INFOGRAPHIC, - script_html, - user_language, - ) - else: - if existing_html_block: - original_content = self._remove_existing_html(original_content) - final_html = self._merge_html( - existing_html_block, - content_html, - CSS_TEMPLATE_INFOGRAPHIC, - script_html, - user_language, - ) - else: - final_html = self._merge_html( - "", - content_html, - CSS_TEMPLATE_INFOGRAPHIC, - script_html, - user_language, - ) - - # 检查输出模式 - if self.valves.OUTPUT_MODE == "image": - # 图片模式:使用 JavaScript 渲染并嵌入为 Markdown 图片 - chat_ctx = self._get_chat_context(body, __metadata__) - chat_id = chat_ctx["chat_id"] - message_id = chat_ctx["message_id"] - - await self._emit_status( - __event_emitter__, - "📊 信息图: 正在渲染图片...", - False, - ) - - if __event_call__: - js_code = self._generate_image_js_code( - unique_id=unique_id, - chat_id=chat_id, - message_id=message_id, - infographic_syntax=infographic_syntax, - ) - - await __event_call__( - { - "type": "execute", - "data": {"code": js_code}, - } - ) - - await self._emit_status( - __event_emitter__, "✅ 信息图: 图片生成完成!", True - ) - await self._emit_notification( - __event_emitter__, - f"📊 信息图图片已生成,{user_name}!", - "success", - ) - logger.info("信息图生成完成(图片模式)") - return body - - # HTML 模式(默认):嵌入为 HTML 块 - html_embed_tag = f"```html\n{final_html}\n```" - body["messages"][-1]["content"] = f"{original_content}\n\n{html_embed_tag}" - - await self._emit_status(__event_emitter__, "✅ 信息图: 生成完成!", True) - await self._emit_notification( - __event_emitter__, - f"📊 信息图已生成,{user_name}!", - "success", - ) - logger.info("信息图生成完成") - - except Exception as e: - error_message = f"信息图处理失败: {str(e)}" - logger.error(f"信息图错误: {error_message}", exc_info=True) - user_facing_error = f"抱歉,信息图在处理时遇到错误: {str(e)}。\n请检查Open WebUI后端日志获取更多详情。" - body["messages"][-1][ - "content" - ] = f"{original_content}\n\n❌ **错误:** {user_facing_error}" - - await self._emit_status(__event_emitter__, "❌ 信息图: 生成失败", True) - await self._emit_notification( - __event_emitter__, f"❌ 信息图生成失败, {user_name}!", "error" - ) - - return body diff --git a/plugins/actions/infographic/v1.6.0.md b/plugins/actions/infographic/v1.6.0.md new file mode 100644 index 0000000..cce2d78 --- /dev/null +++ b/plugins/actions/infographic/v1.6.0.md @@ -0,0 +1,12 @@ +# v1.6.0 Release Notes + +This release is a major upgrade introducing 12-language i18n support frameworks and full context dark/light mode sniffing backbones to prevent card inverse overlaps conflicts natively. + +## New Features +- **12-Language I18n Skeleton Framework**: Full structural fallback algorithms mapped onto global dictionary namespaces. +- **Dagre Flow Layout View** (`relation-dagre`): visual pipeline supporting relationship visualizer pipelines trees. +- **Environment Theme Sniffe Context**: Smooth dark/light status conditioning automatically injected onto visual color palettes adaptation pipelines. + +## Capability & Validation Alignments +- **Cascad Class Shifters**: Integrated child text self-destabilizers lifting SVG contrasts automatically inside container frameworks override pipelines. +- **Strict Data Nodes Layout mapping**: Enforced items index indexes limiting structural interpreting limits during adaptive operations. diff --git a/plugins/actions/infographic/v1.6.0_CN.md b/plugins/actions/infographic/v1.6.0_CN.md new file mode 100644 index 0000000..310e7d3 --- /dev/null +++ b/plugins/actions/infographic/v1.6.0_CN.md @@ -0,0 +1,13 @@ +# v1.6.0 版本发布说明 + +本版本是一次重磅升级,引入了完整的 12 语种标准 i18n(国际化)支持架构,并打通了全链路外壳多阶环境背景(Dark/Light 模式)降维检测,强效解决了由于浅色底卡片跟深色文字覆盖时的低对比度排斥问题。 + +## 新功能 +- **12 语种标准 i18n 翻译骨架**:对标 Mindmap 全量级翻译映射字典以及 variant 状态机转移降维 fallback 处理。 +- **Dagre 关系代数 flow 网络看板视图** (`relation-dagre`): 对应 PR #161 全链,支持有向节点、依赖连线关系的组件流向与骨骼动画渲染。 +- **外壳背景环境自动探测 (Theme Sniffing)**:智能嗅探外部明暗样式并在渲染板壳中追加 `.dark` 等级控制自适应穿透。 + +## 规则校准与对齐 +- **内聚容器反转对比度**:添加子代文字 `color: currentcolor !important` 等联级状态,百分百确保渲染视图清可见。 +- **模板主索引映射表对撞规范**:明确对应 `list-*` $\rightarrow$ `lists`,强控解析抗崩。 +- **Palette 裸排严防崩键**:加强提示词,强制大模型在 theme 节中 palette 禁止掺入引号或逗号,避免样式崩坏。