diff --git a/.github/workflows/publish_new_plugin.yml b/.github/workflows/publish_new_plugin.yml
new file mode 100644
index 0000000..2de2fb1
--- /dev/null
+++ b/.github/workflows/publish_new_plugin.yml
@@ -0,0 +1,68 @@
+name: Publish New Plugin
+
+on:
+ workflow_dispatch:
+ inputs:
+ plugin_dir:
+ description: 'Plugin directory (e.g., plugins/actions/deep-dive)'
+ required: true
+ type: string
+ dry_run:
+ description: 'Dry run mode (preview only)'
+ required: false
+ type: boolean
+ default: false
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.x'
+
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install requests
+
+ - name: Validate plugin directory
+ run: |
+ if [ ! -d "${{ github.event.inputs.plugin_dir }}" ]; then
+ echo "❌ Error: Directory '${{ github.event.inputs.plugin_dir }}' does not exist"
+ exit 1
+ fi
+ echo "✅ Found plugin directory: ${{ github.event.inputs.plugin_dir }}"
+ ls -la "${{ github.event.inputs.plugin_dir }}"
+
+ - name: Publish Plugin
+ env:
+ OPENWEBUI_API_KEY: ${{ secrets.OPENWEBUI_API_KEY }}
+ run: |
+ if [ "${{ github.event.inputs.dry_run }}" = "true" ]; then
+ echo "🔍 Dry run mode - previewing..."
+ python scripts/publish_plugin.py --new "${{ github.event.inputs.plugin_dir }}" --dry-run
+ else
+ echo "🚀 Publishing plugin..."
+ python scripts/publish_plugin.py --new "${{ github.event.inputs.plugin_dir }}"
+ fi
+
+ - name: Commit changes (if ID was added)
+ if: ${{ github.event.inputs.dry_run != 'true' }}
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+
+ # Check if there are changes to commit
+ if git diff --quiet; then
+ echo "No changes to commit"
+ else
+ git add "${{ github.event.inputs.plugin_dir }}"
+ git commit -m "feat: add openwebui_id to ${{ github.event.inputs.plugin_dir }}"
+ git push
+ echo "✅ Committed and pushed openwebui_id changes"
+ fi
diff --git a/docs/plugins/actions/deep-dive.md b/docs/plugins/actions/deep-dive.md
new file mode 100644
index 0000000..bb3979a
--- /dev/null
+++ b/docs/plugins/actions/deep-dive.md
@@ -0,0 +1,111 @@
+# Deep Dive
+
+Action
+v1.0.0
+
+A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.
+
+---
+
+## Overview
+
+The Deep Dive plugin transforms how you understand complex content by guiding you through a structured thinking process. Rather than just summarizing, it deconstructs content across four phases:
+
+- **🔍 The Context (What?)**: Panoramic view of the situation and background
+- **🧠 The Logic (Why?)**: Deconstruction of reasoning and mental models
+- **💎 The Insight (So What?)**: Non-obvious value and hidden implications
+- **🚀 The Path (Now What?)**: Specific, prioritized strategic actions
+
+## Features
+
+- :material-brain: **Thinking Chain**: Complete structured analysis process
+- :material-eye: **Deep Understanding**: Reveals hidden assumptions and blind spots
+- :material-lightbulb-on: **Insight Extraction**: Finds the "Aha!" moments
+- :material-rocket-launch: **Action Oriented**: Translates understanding into actionable steps
+- :material-theme-light-dark: **Theme Adaptive**: Auto-adapts to OpenWebUI light/dark theme
+- :material-translate: **Multi-language**: Outputs in user's preferred language
+
+---
+
+## Installation
+
+1. Download the plugin file: [`deep_dive.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/deep-dive)
+2. Upload to OpenWebUI: **Admin Panel** → **Settings** → **Functions**
+3. Enable the plugin
+
+---
+
+## Usage
+
+1. Provide any long text, article, or meeting notes in the chat
+2. Click the **Deep Dive** button in the message action bar
+3. Follow the visual timeline from Context → Logic → Insight → Path
+
+---
+
+## Configuration
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `SHOW_STATUS` | boolean | `true` | Show status updates during processing |
+| `MODEL_ID` | string | `""` | LLM model for analysis (empty = current model) |
+| `MIN_TEXT_LENGTH` | integer | `200` | Minimum text length for analysis |
+| `CLEAR_PREVIOUS_HTML` | boolean | `true` | Clear previous plugin results |
+| `MESSAGE_COUNT` | integer | `1` | Number of recent messages to analyze |
+
+---
+
+## Theme Support
+
+Deep Dive automatically adapts to OpenWebUI's light/dark theme:
+
+- Detects theme from parent document `` tag
+- Falls back to `html/body` class or `data-theme` attribute
+- Uses system preference `prefers-color-scheme: dark` as last resort
+
+!!! tip "For Best Results"
+ Enable **iframe Sandbox Allow Same Origin** in OpenWebUI:
+ **Settings** → **Interface** → **Artifacts** → Check **iframe Sandbox Allow Same Origin**
+
+---
+
+## Example Output
+
+The plugin generates a beautiful structured timeline:
+
+```
+┌─────────────────────────────────────┐
+│ 🌊 Deep Dive Analysis │
+│ 👤 User 📅 Date 📊 Word count │
+├─────────────────────────────────────┤
+│ 🔍 Phase 01: The Context │
+│ [High-level panoramic view] │
+│ │
+│ 🧠 Phase 02: The Logic │
+│ • Reasoning structure... │
+│ • Hidden assumptions... │
+│ │
+│ 💎 Phase 03: The Insight │
+│ • Non-obvious value... │
+│ • Blind spots revealed... │
+│ │
+│ 🚀 Phase 04: The Path │
+│ ▸ Priority Action 1... │
+│ ▸ Priority Action 2... │
+└─────────────────────────────────────┘
+```
+
+---
+
+## Requirements
+
+!!! note "Prerequisites"
+ - OpenWebUI v0.3.0 or later
+ - Uses the active LLM model for analysis
+ - Requires `markdown` Python package
+
+---
+
+## Source Code
+
+[:fontawesome-brands-github: View on GitHub](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/deep-dive){ .md-button }
diff --git a/docs/plugins/actions/deep-dive.zh.md b/docs/plugins/actions/deep-dive.zh.md
new file mode 100644
index 0000000..d577a3c
--- /dev/null
+++ b/docs/plugins/actions/deep-dive.zh.md
@@ -0,0 +1,111 @@
+# 精读 (Deep Dive)
+
+Action
+v1.0.0
+
+全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。
+
+---
+
+## 概述
+
+精读插件改变了您理解复杂内容的方式,通过结构化的思维过程引导您进行深度分析。它不仅仅是摘要,而是从四个阶段解构内容:
+
+- **🔍 全景 (The Context)**: 情境与背景的高层级全景视图
+- **🧠 脉络 (The Logic)**: 解构底层推理逻辑与思维模型
+- **💎 洞察 (The Insight)**: 提取非显性价值与隐藏含义
+- **🚀 路径 (The Path)**: 具体的、按优先级排列的战略行动
+
+## 功能特性
+
+- :material-brain: **思维链**: 完整的结构化分析过程
+- :material-eye: **深度理解**: 揭示隐藏的假设和思维盲点
+- :material-lightbulb-on: **洞察提取**: 发现"原来如此"的时刻
+- :material-rocket-launch: **行动导向**: 将深度理解转化为可执行步骤
+- :material-theme-light-dark: **主题自适应**: 自动适配 OpenWebUI 深色/浅色主题
+- :material-translate: **多语言**: 以用户偏好语言输出
+
+---
+
+## 安装
+
+1. 下载插件文件: [`deep_dive_cn.py`](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/deep-dive)
+2. 上传到 OpenWebUI: **管理面板** → **设置** → **Functions**
+3. 启用插件
+
+---
+
+## 使用方法
+
+1. 在聊天中提供任何长文本、文章或会议记录
+2. 点击消息操作栏中的 **精读** 按钮
+3. 沿着视觉时间轴从"全景"探索到"路径"
+
+---
+
+## 配置参数
+
+| 选项 | 类型 | 默认值 | 描述 |
+|------|------|--------|------|
+| `SHOW_STATUS` | boolean | `true` | 处理过程中是否显示状态更新 |
+| `MODEL_ID` | string | `""` | 用于分析的 LLM 模型(空 = 当前模型) |
+| `MIN_TEXT_LENGTH` | integer | `200` | 分析所需的最小文本长度 |
+| `CLEAR_PREVIOUS_HTML` | boolean | `true` | 是否清除之前的插件结果 |
+| `MESSAGE_COUNT` | integer | `1` | 要分析的最近消息数量 |
+
+---
+
+## 主题支持
+
+精读插件自动适配 OpenWebUI 的深色/浅色主题:
+
+- 从父文档 `` 标签检测主题
+- 回退到 `html/body` 的 class 或 `data-theme` 属性
+- 最后使用系统偏好 `prefers-color-scheme: dark`
+
+!!! tip "最佳效果"
+ 请在 OpenWebUI 中启用 **iframe Sandbox Allow Same Origin**:
+ **设置** → **界面** → **Artifacts** → 勾选 **iframe Sandbox Allow Same Origin**
+
+---
+
+## 输出示例
+
+插件生成精美的结构化时间轴:
+
+```
+┌─────────────────────────────────────┐
+│ 📖 精读分析报告 │
+│ 👤 用户 📅 日期 📊 字数 │
+├─────────────────────────────────────┤
+│ 🔍 阶段 01: 全景 (The Context) │
+│ [高层级全景视图内容] │
+│ │
+│ 🧠 阶段 02: 脉络 (The Logic) │
+│ • 推理结构分析... │
+│ • 隐藏假设识别... │
+│ │
+│ 💎 阶段 03: 洞察 (The Insight) │
+│ • 非显性价值提取... │
+│ • 思维盲点揭示... │
+│ │
+│ 🚀 阶段 04: 路径 (The Path) │
+│ ▸ 优先级行动 1... │
+│ ▸ 优先级行动 2... │
+└─────────────────────────────────────┘
+```
+
+---
+
+## 系统要求
+
+!!! note "前提条件"
+ - OpenWebUI v0.3.0 或更高版本
+ - 使用当前活跃的 LLM 模型进行分析
+ - 需要 `markdown` Python 包
+
+---
+
+## 源代码
+
+[:fontawesome-brands-github: 在 GitHub 上查看](https://github.com/Fu-Jie/awesome-openwebui/tree/main/plugins/actions/deep-dive){ .md-button }
diff --git a/docs/plugins/actions/index.md b/docs/plugins/actions/index.md
index 31208f4..133d687 100644
--- a/docs/plugins/actions/index.md
+++ b/docs/plugins/actions/index.md
@@ -67,15 +67,15 @@ Actions are interactive plugins that:
[:octicons-arrow-right-24: Documentation](export-to-word.md)
-- :material-text-box-search:{ .lg .middle } **Summary**
+- :material-brain:{ .lg .middle } **Deep Dive**
---
- Generate concise summaries of long text content with key points extraction.
+ A comprehensive thinking lens that dives deep into any content - Context → Logic → Insight → Path. Supports theme auto-adaptation.
- **Version:** 0.1.0
+ **Version:** 1.0.0
- [:octicons-arrow-right-24: Documentation](summary.md)
+ [:octicons-arrow-right-24: Documentation](deep-dive.md)
- :material-image-text:{ .lg .middle } **Infographic to Markdown**
diff --git a/docs/plugins/actions/index.zh.md b/docs/plugins/actions/index.zh.md
index 3d82ca5..810f5be 100644
--- a/docs/plugins/actions/index.zh.md
+++ b/docs/plugins/actions/index.zh.md
@@ -67,15 +67,15 @@ Actions 是交互式插件,能够:
[:octicons-arrow-right-24: 查看文档](export-to-word.md)
-- :material-text-box-search:{ .lg .middle } **Summary**
+- :material-brain:{ .lg .middle } **精读 (Deep Dive)**
---
- 对长文本进行精简总结,提取要点。
+ 全方位的思维透镜 —— 全景 → 脉络 → 洞察 → 路径。支持主题自适应。
- **版本:** 0.1.0
+ **版本:** 1.0.0
- [:octicons-arrow-right-24: 查看文档](summary.md)
+ [:octicons-arrow-right-24: 查看文档](deep-dive.zh.md)
- :material-image-text:{ .lg .middle } **信息图转 Markdown**
diff --git a/plugins/actions/deep-dive/README.md b/plugins/actions/deep-dive/README.md
new file mode 100644
index 0000000..5867fce
--- /dev/null
+++ b/plugins/actions/deep-dive/README.md
@@ -0,0 +1,83 @@
+# 🌊 Deep Dive
+
+**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.0.0 | **Project:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
+
+A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.
+
+## 🔥 What's New in v1.0.0
+
+- ✨ **Thinking Chain Structure**: Moves from surface understanding to deep strategic action.
+- 🔍 **Phase 01: The Context**: Panoramic view of the situation and background.
+- 🧠 **Phase 02: The Logic**: Deconstruction of the underlying reasoning and mental models.
+- 💎 **Phase 03: The Insight**: Extraction of non-obvious value and hidden implications.
+- 🚀 **Phase 04: The Path**: Definition of specific, prioritized strategic directions.
+- 🎨 **Premium UI**: Modern, process-oriented design with a "Thinking Line" timeline.
+- 🌗 **Theme Adaptive**: Automatically adapts to OpenWebUI's light/dark theme.
+
+## ✨ Key Features
+
+- 🌊 **Deep Thinking**: Not just a summary, but a full deconstruction of content.
+- 🧠 **Logical Analysis**: Reveals how arguments are built and identifies hidden assumptions.
+- 💎 **Value Extraction**: Finds the "Aha!" moments and blind spots.
+- 🚀 **Action Oriented**: Translates deep understanding into immediate, actionable steps.
+- 🌍 **Multi-language**: Automatically adapts to the user's preferred language.
+- 🌗 **Theme Support**: Seamlessly switches between light and dark themes based on OpenWebUI settings.
+
+## 🚀 How to Use
+
+1. **Input Content**: Provide any text, article, or meeting notes in the chat.
+2. **Trigger Deep Dive**: Click the **Deep Dive** action button.
+3. **Explore the Chain**: Follow the visual timeline from Context to Path.
+
+## ⚙️ Configuration (Valves)
+
+| Parameter | Default | Description |
+| :--- | :--- | :--- |
+| **Show Status (SHOW_STATUS)** | `True` | Whether to show status updates during the thinking process. |
+| **Model ID (MODEL_ID)** | `Empty` | LLM model for analysis. Empty = use current model. |
+| **Min Text Length (MIN_TEXT_LENGTH)** | `200` | Minimum characters required for a meaningful deep dive. |
+| **Clear Previous HTML (CLEAR_PREVIOUS_HTML)** | `True` | Whether to clear previous plugin results. |
+| **Message Count (MESSAGE_COUNT)** | `1` | Number of recent messages to analyze. |
+
+## 🌗 Theme Support
+
+The plugin automatically detects and adapts to OpenWebUI's theme settings:
+
+- **Detection Priority**:
+ 1. Parent document `` tag
+ 2. Parent document `html/body` class or `data-theme` attribute
+ 3. System preference via `prefers-color-scheme: dark`
+
+- **Requirements**: For best results, enable **iframe Sandbox Allow Same Origin** in OpenWebUI:
+ - Go to **Settings** → **Interface** → **Artifacts** → Check **iframe Sandbox Allow Same Origin**
+
+## 🎨 Visual Preview
+
+The plugin generates a structured thinking timeline:
+
+```
+┌─────────────────────────────────────┐
+│ 🌊 Deep Dive Analysis │
+│ 👤 User 📅 Date 📊 Word count │
+├─────────────────────────────────────┤
+│ 🔍 Phase 01: The Context │
+│ [High-level panoramic view] │
+│ │
+│ 🧠 Phase 02: The Logic │
+│ • Reasoning structure... │
+│ • Hidden assumptions... │
+│ │
+│ 💎 Phase 03: The Insight │
+│ • Non-obvious value... │
+│ • Blind spots revealed... │
+│ │
+│ 🚀 Phase 04: The Path │
+│ ▸ Priority Action 1... │
+│ ▸ Priority Action 2... │
+└─────────────────────────────────────┘
+```
+
+## 📂 Files
+
+- `deep_dive.py` - English version
+- `deep_dive_cn.py` - Chinese version (精读)
diff --git a/plugins/actions/deep-dive/README_CN.md b/plugins/actions/deep-dive/README_CN.md
new file mode 100644
index 0000000..ca6ac28
--- /dev/null
+++ b/plugins/actions/deep-dive/README_CN.md
@@ -0,0 +1,83 @@
+# 📖 精读
+
+**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 1.0.0 | **项目:** [Awesome OpenWebUI](https://github.com/Fu-Jie/awesome-openwebui)
+
+全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。
+
+## 🔥 v1.0.0 更新内容
+
+- ✨ **思维链结构**: 从表面理解一步步深入到战略行动。
+- 🔍 **阶段 01: 全景 (The Context)**: 提供情境与背景的高层级全景视图。
+- 🧠 **阶段 02: 脉络 (The Logic)**: 解构底层推理逻辑与思维模型。
+- 💎 **阶段 03: 洞察 (The Insight)**: 提取非显性价值与隐藏的深层含义。
+- 🚀 **阶段 04: 路径 (The Path)**: 定义具体的、按优先级排列的战略方向。
+- 🎨 **高端 UI**: 现代化的过程导向设计,带有"思维导火索"时间轴。
+- 🌗 **主题自适应**: 自动适配 OpenWebUI 的深色/浅色主题。
+
+## ✨ 核心特性
+
+- 📖 **深度思考**: 不仅仅是摘要,而是对内容的全面解构。
+- 🧠 **逻辑分析**: 揭示论点是如何构建的,识别隐藏的假设。
+- 💎 **价值提取**: 发现"原来如此"的时刻与思维盲点。
+- 🚀 **行动导向**: 将深度理解转化为立即、可执行的步骤。
+- 🌍 **多语言支持**: 自动适配用户的偏好语言。
+- 🌗 **主题支持**: 根据 OpenWebUI 设置自动切换深色/浅色主题。
+
+## 🚀 如何使用
+
+1. **输入内容**: 在聊天中提供任何文本、文章或会议记录。
+2. **触发精读**: 点击 **精读** 操作按钮。
+3. **探索思维链**: 沿着视觉时间轴从"全景"探索到"路径"。
+
+## ⚙️ 配置参数 (Valves)
+
+| 参数 | 默认值 | 描述 |
+| :--- | :--- | :--- |
+| **显示状态 (SHOW_STATUS)** | `True` | 是否在思维过程中显示状态更新。 |
+| **模型 ID (MODEL_ID)** | `空` | 用于分析的 LLM 模型。留空 = 使用当前模型。 |
+| **最小文本长度 (MIN_TEXT_LENGTH)** | `200` | 进行有意义的精读所需的最小字符数。 |
+| **清除旧 HTML (CLEAR_PREVIOUS_HTML)** | `True` | 是否清除之前的插件结果。 |
+| **消息数量 (MESSAGE_COUNT)** | `1` | 要分析的最近消息数量。 |
+
+## 🌗 主题支持
+
+插件会自动检测并适配 OpenWebUI 的主题设置:
+
+- **检测优先级**:
+ 1. 父文档 `` 标签
+ 2. 父文档 `html/body` 的 class 或 `data-theme` 属性
+ 3. 系统偏好 `prefers-color-scheme: dark`
+
+- **环境要求**: 为获得最佳效果,请在 OpenWebUI 中启用 **iframe Sandbox Allow Same Origin**:
+ - 进入 **设置** → **界面** → **Artifacts** → 勾选 **iframe Sandbox Allow Same Origin**
+
+## 🎨 视觉预览
+
+插件生成结构化的思维时间轴:
+
+```
+┌─────────────────────────────────────┐
+│ 📖 精读分析报告 │
+│ 👤 用户 📅 日期 📊 字数 │
+├─────────────────────────────────────┤
+│ 🔍 阶段 01: 全景 (The Context) │
+│ [高层级全景视图内容] │
+│ │
+│ 🧠 阶段 02: 脉络 (The Logic) │
+│ • 推理结构分析... │
+│ • 隐藏假设识别... │
+│ │
+│ 💎 阶段 03: 洞察 (The Insight) │
+│ • 非显性价值提取... │
+│ • 思维盲点揭示... │
+│ │
+│ 🚀 阶段 04: 路径 (The Path) │
+│ ▸ 优先级行动 1... │
+│ ▸ 优先级行动 2... │
+└─────────────────────────────────────┘
+```
+
+## 📂 文件说明
+
+- `deep_dive.py` - 英文版 (Deep Dive)
+- `deep_dive_cn.py` - 中文版 (精读)
diff --git a/plugins/actions/deep-dive/deep_dive.png b/plugins/actions/deep-dive/deep_dive.png
new file mode 100644
index 0000000..350a69d
Binary files /dev/null and b/plugins/actions/deep-dive/deep_dive.png differ
diff --git a/plugins/actions/deep-dive/deep_dive.py b/plugins/actions/deep-dive/deep_dive.py
new file mode 100644
index 0000000..72372a4
--- /dev/null
+++ b/plugins/actions/deep-dive/deep_dive.py
@@ -0,0 +1,884 @@
+"""
+title: Deep Dive
+author: Fu-Jie
+author_url: https://github.com/Fu-Jie
+funding_url: https://github.com/Fu-Jie/awesome-openwebui
+version: 1.0.0
+icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg==
+requirements: markdown
+description: A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.
+"""
+
+# Standard library imports
+import re
+import logging
+from typing import Optional, Dict, Any, Callable, Awaitable
+from datetime import datetime
+
+# Third-party imports
+from pydantic import BaseModel, Field
+from fastapi import Request
+import markdown
+
+# OpenWebUI imports
+from open_webui.utils.chat import generate_chat_completion
+from open_webui.models.users import Users
+
+# Logging setup
+logging.basicConfig(
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+)
+logger = logging.getLogger(__name__)
+
+# =================================================================
+# HTML Template - Process-Oriented Design with Theme Support
+# =================================================================
+HTML_WRAPPER_TEMPLATE = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+# =================================================================
+# LLM Prompts - Deep Dive Thinking Chain
+# =================================================================
+
+SYSTEM_PROMPT = """
+You are a Deep Dive Analyst. Your goal is to guide the user through a comprehensive thinking process, moving from surface understanding to deep strategic action.
+
+## Thinking Structure (STRICT)
+
+You MUST analyze the input across these four specific dimensions:
+
+### 1. 🔍 The Context (What?)
+Provide a high-level panoramic view. What is this content about? What is the core situation, background, or problem being addressed? (2-3 paragraphs)
+
+### 2. 🧠 The Logic (Why?)
+Deconstruct the underlying structure. How is the argument built? What is the reasoning, the hidden assumptions, or the mental models at play? (Bullet points)
+
+### 3. 💎 The Insight (So What?)
+Extract the non-obvious value. What are the "Aha!" moments? What are the implications, the blind spots, or the unique perspectives revealed? (Bullet points)
+
+### 4. 🚀 The Path (Now What?)
+Define the strategic direction. What are the specific, prioritized next steps? How can this knowledge be applied immediately? (Actionable steps)
+
+## Rules
+- Output in the user's specified language.
+- Maintain a professional, analytical, yet inspiring tone.
+- Focus on the *process* of understanding, not just the result.
+- No greetings or meta-commentary.
+"""
+
+USER_PROMPT = """
+Initiate a Deep Dive into the following content:
+
+**User Context:**
+- User: {user_name}
+- Time: {current_date_time_str}
+- Language: {user_language}
+
+**Content to Analyze:**
+```
+{long_text_content}
+```
+
+Please execute the full thinking chain: Context → Logic → Insight → Path.
+"""
+
+# =================================================================
+# Premium CSS Design - Deep Dive Theme
+# =================================================================
+
+CSS_TEMPLATE = """
+ .deep-dive {
+ font-family: 'Inter', -apple-system, system-ui, sans-serif;
+ color: var(--dd-text-secondary);
+ }
+
+ .dd-header {
+ background: var(--dd-header-gradient);
+ padding: 40px 32px;
+ color: white;
+ position: relative;
+ }
+
+ .dd-header-badge {
+ display: inline-block;
+ padding: 4px 12px;
+ background: rgba(255,255,255,0.1);
+ border: 1px solid rgba(255,255,255,0.2);
+ border-radius: 100px;
+ font-size: 0.75rem;
+ font-weight: 600;
+ letter-spacing: 0.05em;
+ text-transform: uppercase;
+ margin-bottom: 16px;
+ }
+
+ .dd-title {
+ font-size: 2rem;
+ font-weight: 800;
+ margin: 0 0 12px 0;
+ letter-spacing: -0.02em;
+ }
+
+ .dd-meta {
+ display: flex;
+ gap: 20px;
+ font-size: 0.85rem;
+ opacity: 0.7;
+ }
+
+ .dd-body {
+ padding: 32px;
+ display: flex;
+ flex-direction: column;
+ gap: 40px;
+ position: relative;
+ background: var(--dd-bg-primary);
+ }
+
+ /* The Thinking Line */
+ .dd-body::before {
+ content: '';
+ position: absolute;
+ left: 52px;
+ top: 40px;
+ bottom: 40px;
+ width: 2px;
+ background: var(--dd-border);
+ z-index: 0;
+ }
+
+ .dd-step {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ gap: 24px;
+ }
+
+ .dd-step-icon {
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ background: var(--dd-bg-primary);
+ border: 2px solid var(--dd-border);
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.03);
+ transition: all 0.3s ease;
+ }
+
+ .dd-step:hover .dd-step-icon {
+ border-color: var(--dd-accent);
+ transform: scale(1.1);
+ }
+
+ .dd-step-content {
+ flex: 1;
+ }
+
+ .dd-step-label {
+ font-size: 0.75rem;
+ font-weight: 700;
+ color: var(--dd-accent);
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ margin-bottom: 4px;
+ }
+
+ .dd-step-title {
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: var(--dd-text-primary);
+ margin: 0 0 16px 0;
+ }
+
+ .dd-text {
+ line-height: 1.7;
+ font-size: 1rem;
+ }
+
+ .dd-text p { margin-bottom: 16px; }
+ .dd-text p:last-child { margin-bottom: 0; }
+
+ .dd-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: grid;
+ gap: 12px;
+ }
+
+ .dd-list-item {
+ background: var(--dd-bg-secondary);
+ padding: 16px 20px;
+ border-radius: 12px;
+ border-left: 4px solid var(--dd-border);
+ transition: all 0.2s ease;
+ }
+
+ .dd-list-item:hover {
+ background: var(--dd-bg-tertiary);
+ border-left-color: var(--dd-accent);
+ transform: translateX(4px);
+ }
+
+ .dd-list-item strong {
+ color: var(--dd-text-primary);
+ display: block;
+ margin-bottom: 4px;
+ }
+
+ .dd-path-item {
+ background: var(--dd-accent-soft);
+ border-left-color: var(--dd-accent);
+ }
+
+ .dd-footer {
+ padding: 24px 32px;
+ background: var(--dd-bg-secondary);
+ border-top: 1px solid var(--dd-border);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 0.8rem;
+ color: var(--dd-text-dim);
+ }
+
+ .dd-tag {
+ padding: 2px 8px;
+ background: var(--dd-bg-tertiary);
+ border-radius: 4px;
+ font-weight: 600;
+ }
+
+ .dd-text code,
+ .dd-list-item code {
+ background: var(--dd-code-bg);
+ color: var(--dd-text-primary);
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-family: 'SF Mono', 'Consolas', 'Monaco', monospace;
+ font-size: 0.85em;
+ }
+
+ .dd-list-item em {
+ font-style: italic;
+ color: var(--dd-text-dim);
+ }
+"""
+
+CONTENT_TEMPLATE = """
+
+
+
+
+
+
🔍
+
+
Phase 01
+
The Context
+
{context_html}
+
+
+
+
+
+
🧠
+
+
Phase 02
+
The Logic
+
{logic_html}
+
+
+
+
+
+
💎
+
+
Phase 03
+
The Insight
+
{insight_html}
+
+
+
+
+
+
🚀
+
+
Phase 04
+
The Path
+
{path_html}
+
+
+
+
+
+"""
+
+
+class Action:
+ class Valves(BaseModel):
+ SHOW_STATUS: bool = Field(
+ default=True,
+ description="Whether to show operation status updates.",
+ )
+ MODEL_ID: str = Field(
+ default="",
+ description="LLM Model ID for analysis. Empty = use current model.",
+ )
+ MIN_TEXT_LENGTH: int = Field(
+ default=200,
+ description="Minimum text length for deep dive (chars).",
+ )
+ CLEAR_PREVIOUS_HTML: bool = Field(
+ default=True,
+ description="Whether to clear previous plugin results.",
+ )
+ MESSAGE_COUNT: int = Field(
+ default=1,
+ description="Number of recent messages to analyze.",
+ )
+
+ def __init__(self):
+ self.valves = self.Valves()
+
+ def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> Dict[str, str]:
+ """Safely extracts user context information."""
+ if isinstance(__user__, (list, tuple)):
+ user_data = __user__[0] if __user__ else {}
+ elif isinstance(__user__, dict):
+ user_data = __user__
+ else:
+ user_data = {}
+
+ return {
+ "user_id": user_data.get("id", "unknown_user"),
+ "user_name": user_data.get("name", "User"),
+ "user_language": user_data.get("language", "en-US"),
+ }
+
+ def _process_llm_output(self, llm_output: str) -> Dict[str, str]:
+ """Parse LLM output and convert to styled HTML."""
+ # Extract sections using flexible regex
+ context_match = re.search(
+ r"###\s*1\.\s*🔍?\s*The Context\s*\((.*?)\)\s*\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+ logic_match = re.search(
+ r"###\s*2\.\s*🧠?\s*The Logic\s*\((.*?)\)\s*\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+ insight_match = re.search(
+ r"###\s*3\.\s*💎?\s*The Insight\s*\((.*?)\)\s*\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+ path_match = re.search(
+ r"###\s*4\.\s*🚀?\s*The Path\s*\((.*?)\)\s*\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+
+ # Fallback if numbering is different
+ if not context_match:
+ context_match = re.search(
+ r"###\s*🔍?\s*The Context.*?\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+ if not logic_match:
+ logic_match = re.search(
+ r"###\s*🧠?\s*The Logic.*?\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+ if not insight_match:
+ insight_match = re.search(
+ r"###\s*💎?\s*The Insight.*?\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+ if not path_match:
+ path_match = re.search(
+ r"###\s*🚀?\s*The Path.*?\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+
+ context_md = (
+ context_match.group(1 if context_match.lastindex == 1 else 2).strip()
+ if context_match
+ else ""
+ )
+ logic_md = (
+ logic_match.group(1 if logic_match.lastindex == 1 else 2).strip()
+ if logic_match
+ else ""
+ )
+ insight_md = (
+ insight_match.group(1 if insight_match.lastindex == 1 else 2).strip()
+ if insight_match
+ else ""
+ )
+ path_md = (
+ path_match.group(1 if path_match.lastindex == 1 else 2).strip()
+ if path_match
+ else ""
+ )
+
+ if not any([context_md, logic_md, insight_md, path_md]):
+ context_md = llm_output.strip()
+ logger.warning("LLM output did not follow format. Using as context.")
+
+ md_extensions = ["nl2br"]
+
+ context_html = (
+ markdown.markdown(context_md, extensions=md_extensions)
+ if context_md
+ else 'No context extracted.
'
+ )
+ logic_html = (
+ self._process_list_items(logic_md, "logic")
+ if logic_md
+ else 'No logic deconstructed.
'
+ )
+ insight_html = (
+ self._process_list_items(insight_md, "insight")
+ if insight_md
+ else 'No insights found.
'
+ )
+ path_html = (
+ self._process_list_items(path_md, "path")
+ if path_md
+ else 'No path defined.
'
+ )
+
+ return {
+ "context_html": context_html,
+ "logic_html": logic_html,
+ "insight_html": insight_html,
+ "path_html": path_html,
+ }
+
+ def _process_list_items(self, md_content: str, section_type: str) -> str:
+ """Convert markdown list to styled HTML cards with full markdown support."""
+ lines = md_content.strip().split("\n")
+ items = []
+ current_paragraph = []
+
+ for line in lines:
+ line = line.strip()
+
+ # Check for list item (bullet or numbered)
+ bullet_match = re.match(r"^[-*]\s+(.+)$", line)
+ numbered_match = re.match(r"^\d+\.\s+(.+)$", line)
+
+ if bullet_match or numbered_match:
+ # Flush any accumulated paragraph
+ if current_paragraph:
+ para_text = " ".join(current_paragraph)
+ para_html = self._convert_inline_markdown(para_text)
+ items.append(f"{para_html}
")
+ current_paragraph = []
+
+ # Extract the list item content
+ text = (
+ bullet_match.group(1) if bullet_match else numbered_match.group(1)
+ )
+
+ # Handle bold title pattern: **Title:** Description or **Title**: Description
+ title_match = re.match(r"\*\*(.+?)\*\*[:\s]*(.*)$", text)
+ if title_match:
+ title = self._convert_inline_markdown(title_match.group(1))
+ desc = self._convert_inline_markdown(title_match.group(2).strip())
+ path_class = "dd-path-item" if section_type == "path" else ""
+ item_html = f'{title}{desc}
'
+ else:
+ text_html = self._convert_inline_markdown(text)
+ path_class = "dd-path-item" if section_type == "path" else ""
+ item_html = (
+ f'{text_html}
'
+ )
+ items.append(item_html)
+ elif line and not line.startswith("#"):
+ # Accumulate paragraph text
+ current_paragraph.append(line)
+ elif not line and current_paragraph:
+ # Empty line ends paragraph
+ para_text = " ".join(current_paragraph)
+ para_html = self._convert_inline_markdown(para_text)
+ items.append(f"{para_html}
")
+ current_paragraph = []
+
+ # Flush remaining paragraph
+ if current_paragraph:
+ para_text = " ".join(current_paragraph)
+ para_html = self._convert_inline_markdown(para_text)
+ items.append(f"{para_html}
")
+
+ if items:
+ return f'{" ".join(items)}
'
+ return f'No items found.
'
+
+ def _convert_inline_markdown(self, text: str) -> str:
+ """Convert inline markdown (bold, italic, code) to HTML."""
+ # Convert inline code: `code` -> code
+ text = re.sub(r"`([^`]+)`", r"\1", text)
+ # Convert bold: **text** -> text
+ text = re.sub(r"\*\*(.+?)\*\*", r"\1", text)
+ # Convert italic: *text* -> text (but not inside **)
+ text = re.sub(r"(?\1", text)
+ return text
+
+ async def _emit_status(
+ self,
+ emitter: Optional[Callable[[Any], Awaitable[None]]],
+ description: str,
+ done: bool = False,
+ ):
+ """Emits a status update event."""
+ if self.valves.SHOW_STATUS and emitter:
+ await emitter(
+ {"type": "status", "data": {"description": description, "done": done}}
+ )
+
+ async def _emit_notification(
+ self,
+ emitter: Optional[Callable[[Any], Awaitable[None]]],
+ content: str,
+ ntype: str = "info",
+ ):
+ """Emits a notification event."""
+ if emitter:
+ await emitter(
+ {"type": "notification", "data": {"type": ntype, "content": content}}
+ )
+
+ def _remove_existing_html(self, content: str) -> str:
+ """Removes existing plugin-generated HTML."""
+ pattern = r"```html\s*[\s\S]*?```"
+ return re.sub(pattern, "", content).strip()
+
+ def _extract_text_content(self, content) -> str:
+ """Extract text from message content."""
+ if isinstance(content, str):
+ return content
+ elif isinstance(content, list):
+ 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: str,
+ new_content: str,
+ new_styles: str = "",
+ user_language: str = "en-US",
+ ) -> str:
+ """Merges new content into HTML container."""
+ if "" in existing_html:
+ base_html = re.sub(r"^```html\s*", "", existing_html)
+ base_html = re.sub(r"\s*```$", "", base_html)
+ else:
+ base_html = HTML_WRAPPER_TEMPLATE.replace("{user_language}", user_language)
+
+ wrapped = 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}\n",
+ )
+
+ return base_html.strip()
+
+ def _build_content_html(self, context: dict) -> str:
+ """Build content HTML."""
+ html = CONTENT_TEMPLATE
+ for key, value in context.items():
+ html = html.replace(f"{{{key}}}", str(value))
+ return html
+
+ async def action(
+ self,
+ body: dict,
+ __user__: Optional[Dict[str, Any]] = None,
+ __event_emitter__: Optional[Callable[[Any], Awaitable[None]]] = None,
+ __request__: Optional[Request] = None,
+ ) -> Optional[dict]:
+ logger.info("Action: Deep Dive v1.0.0 started")
+
+ user_ctx = self._get_user_context(__user__)
+ user_id = user_ctx["user_id"]
+ user_name = user_ctx["user_name"]
+ user_language = user_ctx["user_language"]
+
+ now = datetime.now()
+ current_date_time_str = now.strftime("%b %d, %Y %H:%M")
+
+ original_content = ""
+ try:
+ messages = body.get("messages", [])
+ if not messages:
+ raise ValueError("No messages found.")
+
+ message_count = min(self.valves.MESSAGE_COUNT, len(messages))
+ recent_messages = messages[-message_count:]
+
+ aggregated_parts = []
+ for msg in recent_messages:
+ text = self._extract_text_content(msg.get("content"))
+ if text:
+ aggregated_parts.append(text)
+
+ if not aggregated_parts:
+ raise ValueError("No text content found.")
+
+ original_content = "\n\n---\n\n".join(aggregated_parts)
+ word_count = len(original_content.split())
+
+ if len(original_content) < self.valves.MIN_TEXT_LENGTH:
+ msg = f"Content too brief ({len(original_content)} chars). Deep Dive requires at least {self.valves.MIN_TEXT_LENGTH} chars for meaningful analysis."
+ await self._emit_notification(__event_emitter__, msg, "warning")
+ return {"messages": [{"role": "assistant", "content": f"⚠️ {msg}"}]}
+
+ await self._emit_notification(
+ __event_emitter__, "🌊 Initiating Deep Dive thinking process...", "info"
+ )
+ await self._emit_status(
+ __event_emitter__, "🌊 Deep Dive: Analyzing Context & Logic...", False
+ )
+
+ prompt = USER_PROMPT.format(
+ user_name=user_name,
+ current_date_time_str=current_date_time_str,
+ user_language=user_language,
+ long_text_content=original_content,
+ )
+
+ model = self.valves.MODEL_ID or body.get("model")
+ payload = {
+ "model": model,
+ "messages": [
+ {"role": "system", "content": SYSTEM_PROMPT},
+ {"role": "user", "content": prompt},
+ ],
+ "stream": False,
+ }
+
+ user_obj = Users.get_user_by_id(user_id)
+ if not user_obj:
+ raise ValueError(f"User not found: {user_id}")
+
+ response = await generate_chat_completion(__request__, payload, user_obj)
+ llm_output = response["choices"][0]["message"]["content"]
+
+ processed = self._process_llm_output(llm_output)
+
+ context = {
+ "user_name": user_name,
+ "current_date_time_str": current_date_time_str,
+ "word_count": word_count,
+ **processed,
+ }
+
+ content_html = self._build_content_html(context)
+
+ # Handle existing HTML
+ existing = ""
+ match = re.search(
+ r"```html\s*([\s\S]*?)```",
+ original_content,
+ )
+ if match:
+ existing = match.group(1)
+
+ if self.valves.CLEAR_PREVIOUS_HTML or not existing:
+ original_content = self._remove_existing_html(original_content)
+ final_html = self._merge_html(
+ "", content_html, CSS_TEMPLATE, user_language
+ )
+ else:
+ original_content = self._remove_existing_html(original_content)
+ final_html = self._merge_html(
+ existing, content_html, CSS_TEMPLATE, user_language
+ )
+
+ body["messages"][-1][
+ "content"
+ ] = f"{original_content}\n\n```html\n{final_html}\n```"
+
+ await self._emit_status(__event_emitter__, "🌊 Deep Dive complete!", True)
+ await self._emit_notification(
+ __event_emitter__,
+ f"🌊 Deep Dive complete, {user_name}! Thinking chain generated.",
+ "success",
+ )
+
+ except Exception as e:
+ logger.error(f"Deep Dive Error: {e}", exc_info=True)
+ body["messages"][-1][
+ "content"
+ ] = f"{original_content}\n\n❌ **Error:** {str(e)}"
+ await self._emit_status(__event_emitter__, "Deep Dive failed.", True)
+ await self._emit_notification(
+ __event_emitter__, f"Error: {str(e)}", "error"
+ )
+
+ return body
diff --git a/plugins/actions/deep-dive/deep_dive_cn.png b/plugins/actions/deep-dive/deep_dive_cn.png
new file mode 100644
index 0000000..fcf4c78
Binary files /dev/null and b/plugins/actions/deep-dive/deep_dive_cn.png differ
diff --git a/plugins/actions/deep-dive/deep_dive_cn.py b/plugins/actions/deep-dive/deep_dive_cn.py
new file mode 100644
index 0000000..a557fd3
--- /dev/null
+++ b/plugins/actions/deep-dive/deep_dive_cn.py
@@ -0,0 +1,876 @@
+"""
+title: 精读
+author: Fu-Jie
+author_url: https://github.com/Fu-Jie
+funding_url: https://github.com/Fu-Jie/awesome-openwebui
+version: 1.0.0
+icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xMiA3djE0Ii8+PHBhdGggZD0iTTMgMThhMSAxIDAgMCAxLTEtMVY0YTEgMSAwIDAgMSAxLTFoNWE0IDQgMCAwIDEgNCA0IDQgNCAwIDAgMSA0LTRoNWExIDEgMCAwIDEgMSAxdjEzYTEgMSAwIDAgMS0xIDFoLTZhMyAzIDAgMCAwLTMgMyAzIDMgMCAwIDAtMy0zeiIvPjxwYXRoIGQ9Ik02IDEyaDIiLz48cGF0aCBkPSJNMTYgMTJoMiIvPjwvc3ZnPg==
+requirements: markdown
+description: 全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。
+"""
+
+# Standard library imports
+import re
+import logging
+from typing import Optional, Dict, Any, Callable, Awaitable
+from datetime import datetime
+
+# Third-party imports
+from pydantic import BaseModel, Field
+from fastapi import Request
+import markdown
+
+# OpenWebUI imports
+from open_webui.utils.chat import generate_chat_completion
+from open_webui.models.users import Users
+
+# Logging setup
+logging.basicConfig(
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+)
+logger = logging.getLogger(__name__)
+
+# =================================================================
+# HTML 模板 - 过程导向设计,支持主题自适应
+# =================================================================
+HTML_WRAPPER_TEMPLATE = """
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+# =================================================================
+# LLM 提示词 - 深度下潜思维链
+# =================================================================
+
+SYSTEM_PROMPT = """
+你是一位“深度下潜 (Deep Dive)”分析专家。你的目标是引导用户完成一个全面的思维过程,从表面理解深入到战略行动。
+
+## 思维结构 (严格遵守)
+
+你必须从以下四个维度剖析输入内容:
+
+### 1. 🔍 The Context (全景)
+提供一个高层级的全景视图。内容是关于什么的?核心情境、背景或正在解决的问题是什么?(2-3 段话)
+
+### 2. 🧠 The Logic (脉络)
+解构底层结构。论点是如何构建的?其中的推理逻辑、隐藏假设或起作用的思维模型是什么?(列表形式)
+
+### 3. 💎 The Insight (洞察)
+提取非显性的价值。有哪些“原来如此”的时刻?揭示了哪些深层含义、盲点或独特视角?(列表形式)
+
+### 4. 🚀 The Path (路径)
+定义战略方向。具体的、按优先级排列的下一步行动是什么?如何立即应用这些知识?(可执行步骤)
+
+## 规则
+- 使用用户指定的语言输出。
+- 保持专业、分析性且富有启发性的语调。
+- 聚焦于“理解的过程”,而不仅仅是结果。
+- 不要包含寒暄或元对话。
+"""
+
+USER_PROMPT = """
+对以下内容发起“深度下潜”:
+
+**用户上下文:**
+- 用户:{user_name}
+- 时间:{current_date_time_str}
+- 语言:{user_language}
+
+**待分析内容:**
+```
+{long_text_content}
+```
+
+请执行完整的思维链:全景 (Context) → 脉络 (Logic) → 洞察 (Insight) → 路径 (Path)。
+"""
+
+# =================================================================
+# 现代 CSS 设计 - 深度下潜主题
+# =================================================================
+
+CSS_TEMPLATE = """
+ .deep-dive {
+ font-family: 'Inter', -apple-system, system-ui, sans-serif;
+ color: var(--dd-text-secondary);
+ }
+
+ .dd-header {
+ background: var(--dd-header-gradient);
+ padding: 40px 32px;
+ color: white;
+ position: relative;
+ }
+
+ .dd-header-badge {
+ display: inline-block;
+ padding: 4px 12px;
+ background: rgba(255,255,255,0.1);
+ border: 1px solid rgba(255,255,255,0.2);
+ border-radius: 100px;
+ font-size: 0.75rem;
+ font-weight: 600;
+ letter-spacing: 0.05em;
+ text-transform: uppercase;
+ margin-bottom: 16px;
+ }
+
+ .dd-title {
+ font-size: 2rem;
+ font-weight: 800;
+ margin: 0 0 12px 0;
+ letter-spacing: -0.02em;
+ }
+
+ .dd-meta {
+ display: flex;
+ gap: 20px;
+ font-size: 0.85rem;
+ opacity: 0.7;
+ }
+
+ .dd-body {
+ padding: 32px;
+ display: flex;
+ flex-direction: column;
+ gap: 40px;
+ position: relative;
+ background: var(--dd-bg-primary);
+ }
+
+ /* 思维导火索 */
+ .dd-body::before {
+ content: '';
+ position: absolute;
+ left: 52px;
+ top: 40px;
+ bottom: 40px;
+ width: 2px;
+ background: var(--dd-border);
+ z-index: 0;
+ }
+
+ .dd-step {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ gap: 24px;
+ }
+
+ .dd-step-icon {
+ flex-shrink: 0;
+ width: 40px;
+ height: 40px;
+ background: var(--dd-bg-primary);
+ border: 2px solid var(--dd-border);
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.03);
+ transition: all 0.3s ease;
+ }
+
+ .dd-step:hover .dd-step-icon {
+ border-color: var(--dd-accent);
+ transform: scale(1.1);
+ }
+
+ .dd-step-content {
+ flex: 1;
+ }
+
+ .dd-step-label {
+ font-size: 0.75rem;
+ font-weight: 700;
+ color: var(--dd-accent);
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ margin-bottom: 4px;
+ }
+
+ .dd-step-title {
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: var(--dd-text-primary);
+ margin: 0 0 16px 0;
+ }
+
+ .dd-text {
+ line-height: 1.7;
+ font-size: 1rem;
+ }
+
+ .dd-text p { margin-bottom: 16px; }
+ .dd-text p:last-child { margin-bottom: 0; }
+
+ .dd-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: grid;
+ gap: 12px;
+ }
+
+ .dd-list-item {
+ background: var(--dd-bg-secondary);
+ padding: 16px 20px;
+ border-radius: 12px;
+ border-left: 4px solid var(--dd-border);
+ transition: all 0.2s ease;
+ }
+
+ .dd-list-item:hover {
+ background: var(--dd-bg-tertiary);
+ border-left-color: var(--dd-accent);
+ transform: translateX(4px);
+ }
+
+ .dd-list-item strong {
+ color: var(--dd-text-primary);
+ display: block;
+ margin-bottom: 4px;
+ }
+
+ .dd-path-item {
+ background: var(--dd-accent-soft);
+ border-left-color: var(--dd-accent);
+ }
+
+ .dd-footer {
+ padding: 24px 32px;
+ background: var(--dd-bg-secondary);
+ border-top: 1px solid var(--dd-border);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 0.8rem;
+ color: var(--dd-text-dim);
+ }
+
+ .dd-tag {
+ padding: 2px 8px;
+ background: var(--dd-bg-tertiary);
+ border-radius: 4px;
+ font-weight: 600;
+ }
+
+ .dd-text code,
+ .dd-list-item code {
+ background: var(--dd-code-bg);
+ color: var(--dd-text-primary);
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-family: 'SF Mono', 'Consolas', 'Monaco', monospace;
+ font-size: 0.85em;
+ }
+
+ .dd-list-item em {
+ font-style: italic;
+ color: var(--dd-text-dim);
+ }
+"""
+
+CONTENT_TEMPLATE = """
+
+
+
+
+
+
🔍
+
+
Phase 01
+
全景 (The Context)
+
{context_html}
+
+
+
+
+
+
🧠
+
+
Phase 02
+
脉络 (The Logic)
+
{logic_html}
+
+
+
+
+
+
💎
+
+
Phase 03
+
洞察 (The Insight)
+
{insight_html}
+
+
+
+
+
+
🚀
+
+
Phase 04
+
路径 (The Path)
+
{path_html}
+
+
+
+
+
+"""
+
+
+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=200,
+ description="深度下潜所需的最小文本长度(字符)。",
+ )
+ CLEAR_PREVIOUS_HTML: bool = Field(
+ default=True,
+ description="是否清除之前的插件结果。",
+ )
+ MESSAGE_COUNT: int = Field(
+ default=1,
+ description="要分析的最近消息数量。",
+ )
+
+ def __init__(self):
+ self.valves = self.Valves()
+
+ def _get_user_context(self, __user__: Optional[Dict[str, Any]]) -> 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 = {}
+
+ return {
+ "user_id": user_data.get("id", "unknown_user"),
+ "user_name": user_data.get("name", "用户"),
+ "user_language": user_data.get("language", "zh-CN"),
+ }
+
+ def _process_llm_output(self, llm_output: str) -> Dict[str, str]:
+ """解析 LLM 输出并转换为样式化 HTML。"""
+ # 使用灵活的正则提取各部分
+ context_match = re.search(
+ r"###\s*1\.\s*🔍?\s*(?:全景|The Context)\s*(?:\((.*?)\))?\s*\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+ logic_match = re.search(
+ r"###\s*2\.\s*🧠?\s*(?:脉络|The Logic)\s*(?:\((.*?)\))?\s*\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+ insight_match = re.search(
+ r"###\s*3\.\s*💎?\s*(?:洞察|The Insight)\s*(?:\((.*?)\))?\s*\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+ path_match = re.search(
+ r"###\s*4\.\s*🚀?\s*(?:路径|The Path)\s*(?:\((.*?)\))?\s*\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+
+ # 兜底正则
+ if not context_match:
+ context_match = re.search(
+ r"###\s*🔍?\s*(?:全景|The Context).*?\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+ if not logic_match:
+ logic_match = re.search(
+ r"###\s*🧠?\s*(?:脉络|The Logic).*?\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+ if not insight_match:
+ insight_match = re.search(
+ r"###\s*💎?\s*(?:洞察|The Insight).*?\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+ if not path_match:
+ path_match = re.search(
+ r"###\s*🚀?\s*(?:路径|The Path).*?\n(.*?)(?=\n###|$)",
+ llm_output,
+ re.DOTALL | re.IGNORECASE,
+ )
+
+ context_md = (
+ context_match.group(context_match.lastindex).strip()
+ if context_match
+ else ""
+ )
+ logic_md = (
+ logic_match.group(logic_match.lastindex).strip() if logic_match else ""
+ )
+ insight_md = (
+ insight_match.group(insight_match.lastindex).strip()
+ if insight_match
+ else ""
+ )
+ path_md = path_match.group(path_match.lastindex).strip() if path_match else ""
+
+ if not any([context_md, logic_md, insight_md, path_md]):
+ context_md = llm_output.strip()
+ logger.warning("LLM 输出未遵循格式,将作为全景处理。")
+
+ md_extensions = ["nl2br"]
+
+ context_html = (
+ markdown.markdown(context_md, extensions=md_extensions)
+ if context_md
+ else '未能提取全景信息。
'
+ )
+ logic_html = (
+ self._process_list_items(logic_md, "logic")
+ if logic_md
+ else '未能解构脉络。
'
+ )
+ insight_html = (
+ self._process_list_items(insight_md, "insight")
+ if insight_md
+ else '未能发现洞察。
'
+ )
+ path_html = (
+ self._process_list_items(path_md, "path")
+ if path_md
+ else '未能定义路径。
'
+ )
+
+ return {
+ "context_html": context_html,
+ "logic_html": logic_html,
+ "insight_html": insight_html,
+ "path_html": path_html,
+ }
+
+ def _process_list_items(self, md_content: str, section_type: str) -> str:
+ """将 markdown 列表转换为样式化卡片,支持完整的 markdown 格式。"""
+ lines = md_content.strip().split("\n")
+ items = []
+ current_paragraph = []
+
+ for line in lines:
+ line = line.strip()
+
+ # 检查列表项(无序或有序)
+ bullet_match = re.match(r"^[-*]\s+(.+)$", line)
+ numbered_match = re.match(r"^\d+\.\s+(.+)$", line)
+
+ if bullet_match or numbered_match:
+ # 清空累积的段落
+ if current_paragraph:
+ para_text = " ".join(current_paragraph)
+ para_html = self._convert_inline_markdown(para_text)
+ items.append(f"{para_html}
")
+ current_paragraph = []
+
+ # 提取列表项内容
+ text = (
+ bullet_match.group(1) if bullet_match else numbered_match.group(1)
+ )
+
+ # 处理粗体标题模式:**标题:** 描述 或 **标题**: 描述
+ title_match = re.match(r"\*\*(.+?)\*\*[:\s:]*(.*)$", text)
+ if title_match:
+ title = self._convert_inline_markdown(title_match.group(1))
+ desc = self._convert_inline_markdown(title_match.group(2).strip())
+ path_class = "dd-path-item" if section_type == "path" else ""
+ item_html = f'{title}{desc}
'
+ else:
+ text_html = self._convert_inline_markdown(text)
+ path_class = "dd-path-item" if section_type == "path" else ""
+ item_html = (
+ f'{text_html}
'
+ )
+ items.append(item_html)
+ elif line and not line.startswith("#"):
+ # 累积段落文本
+ current_paragraph.append(line)
+ elif not line and current_paragraph:
+ # 空行结束段落
+ para_text = " ".join(current_paragraph)
+ para_html = self._convert_inline_markdown(para_text)
+ items.append(f"{para_html}
")
+ current_paragraph = []
+
+ # 清空剩余段落
+ if current_paragraph:
+ para_text = " ".join(current_paragraph)
+ para_html = self._convert_inline_markdown(para_text)
+ items.append(f"{para_html}
")
+
+ if items:
+ return f'{" ".join(items)}
'
+ return f'未找到条目。
'
+
+ def _convert_inline_markdown(self, text: str) -> str:
+ """将行内 markdown(粗体、斜体、代码)转换为 HTML。"""
+ # 转换行内代码:`code` -> code
+ text = re.sub(r"`([^`]+)`", r"\1", text)
+ # 转换粗体:**text** -> text
+ text = re.sub(r"\*\*(.+?)\*\*", r"\1", text)
+ # 转换斜体:*text* -> text(但不在 ** 内部)
+ text = re.sub(r"(?\1", text)
+ return text
+
+ async def _emit_status(
+ self,
+ emitter: Optional[Callable[[Any], Awaitable[None]]],
+ 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: Optional[Callable[[Any], Awaitable[None]]],
+ content: str,
+ ntype: str = "info",
+ ):
+ """发送通知事件。"""
+ if emitter:
+ await emitter(
+ {"type": "notification", "data": {"type": ntype, "content": content}}
+ )
+
+ 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):
+ 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: str,
+ new_content: str,
+ new_styles: str = "",
+ user_language: str = "zh-CN",
+ ) -> str:
+ """合并新内容到 HTML 容器。"""
+ if "" in existing_html:
+ base_html = re.sub(r"^```html\s*", "", existing_html)
+ base_html = re.sub(r"\s*```$", "", base_html)
+ else:
+ base_html = HTML_WRAPPER_TEMPLATE.replace("{user_language}", user_language)
+
+ wrapped = 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}\n",
+ )
+
+ return base_html.strip()
+
+ def _build_content_html(self, context: dict) -> str:
+ """构建内容 HTML。"""
+ html = CONTENT_TEMPLATE
+ for key, value in context.items():
+ html = html.replace(f"{{{key}}}", str(value))
+ return html
+
+ async def action(
+ self,
+ body: dict,
+ __user__: Optional[Dict[str, Any]] = None,
+ __event_emitter__: Optional[Callable[[Any], Awaitable[None]]] = None,
+ __request__: Optional[Request] = None,
+ ) -> Optional[dict]:
+ logger.info("Action: 精读 v1.0.0 启动")
+
+ user_ctx = self._get_user_context(__user__)
+ user_id = user_ctx["user_id"]
+ user_name = user_ctx["user_name"]
+ user_language = user_ctx["user_language"]
+
+ now = datetime.now()
+ current_date_time_str = now.strftime("%Y年%m月%d日 %H:%M")
+
+ original_content = ""
+ try:
+ messages = body.get("messages", [])
+ if not messages:
+ raise ValueError("未找到消息内容。")
+
+ message_count = min(self.valves.MESSAGE_COUNT, len(messages))
+ recent_messages = messages[-message_count:]
+
+ aggregated_parts = []
+ for msg in recent_messages:
+ text = self._extract_text_content(msg.get("content"))
+ if text:
+ aggregated_parts.append(text)
+
+ if not aggregated_parts:
+ raise ValueError("未找到文本内容。")
+
+ original_content = "\n\n---\n\n".join(aggregated_parts)
+ word_count = len(original_content)
+
+ if len(original_content) < self.valves.MIN_TEXT_LENGTH:
+ msg = f"内容过短({len(original_content)} 字符)。精读至少需要 {self.valves.MIN_TEXT_LENGTH} 字符才能进行有意义的分析。"
+ await self._emit_notification(__event_emitter__, msg, "warning")
+ return {"messages": [{"role": "assistant", "content": f"⚠️ {msg}"}]}
+
+ await self._emit_notification(
+ __event_emitter__, "📖 正在发起精读分析...", "info"
+ )
+ await self._emit_status(
+ __event_emitter__, "📖 精读:正在分析全景与脉络...", False
+ )
+
+ prompt = USER_PROMPT.format(
+ user_name=user_name,
+ current_date_time_str=current_date_time_str,
+ user_language=user_language,
+ long_text_content=original_content,
+ )
+
+ model = self.valves.MODEL_ID or body.get("model")
+ payload = {
+ "model": model,
+ "messages": [
+ {"role": "system", "content": SYSTEM_PROMPT},
+ {"role": "user", "content": prompt},
+ ],
+ "stream": False,
+ }
+
+ user_obj = Users.get_user_by_id(user_id)
+ if not user_obj:
+ raise ValueError(f"未找到用户:{user_id}")
+
+ response = await generate_chat_completion(__request__, payload, user_obj)
+ llm_output = response["choices"][0]["message"]["content"]
+
+ processed = self._process_llm_output(llm_output)
+
+ context = {
+ "user_name": user_name,
+ "current_date_time_str": current_date_time_str,
+ "word_count": word_count,
+ **processed,
+ }
+
+ content_html = self._build_content_html(context)
+
+ # 处理已有 HTML
+ existing = ""
+ match = re.search(
+ r"```html\s*([\s\S]*?)```",
+ original_content,
+ )
+ if match:
+ existing = match.group(1)
+
+ if self.valves.CLEAR_PREVIOUS_HTML or not existing:
+ original_content = self._remove_existing_html(original_content)
+ final_html = self._merge_html(
+ "", content_html, CSS_TEMPLATE, user_language
+ )
+ else:
+ original_content = self._remove_existing_html(original_content)
+ final_html = self._merge_html(
+ existing, content_html, CSS_TEMPLATE, user_language
+ )
+
+ body["messages"][-1][
+ "content"
+ ] = f"{original_content}\n\n```html\n{final_html}\n```"
+
+ await self._emit_status(__event_emitter__, "📖 精读完成!", True)
+ await self._emit_notification(
+ __event_emitter__,
+ f"📖 精读完成,{user_name}!思维链已生成。",
+ "success",
+ )
+
+ except Exception as e:
+ logger.error(f"Deep Dive 错误:{e}", exc_info=True)
+ body["messages"][-1][
+ "content"
+ ] = f"{original_content}\n\n❌ **错误:** {str(e)}"
+ await self._emit_status(__event_emitter__, "精读失败。", True)
+ await self._emit_notification(__event_emitter__, f"错误:{str(e)}", "error")
+
+ return body