2025-12-20 12:34:49 +08:00
"""
title : Deep Reading & Summary
2025-12-20 14:27:37 +08:00
author : Fu - Jie
author_url : https : / / github . com / Fu - Jie
funding_url : https : / / github . com / Fu - Jie / awesome - openwebui
2025-12-20 12:34:49 +08:00
version : 0.1 .0
icon_url : data : image / svg + xml ; base64 , PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0yIDNIMGEyIDIgMCAwIDAgMiAyIi8 + PHBhdGggZD0iTTIyIDNIMjBhMiAyIDAgMCAwLTIgMiIvPjxwYXRoIGQ9Ik0yIDdoMjB2MTRhMiAyIDAgMCAxLTIgMmgtMTZhMiAyIDAgMCAxLTItMnYtMTQiLz48cGF0aCBkPSJNMTEgMTJ2NiIvPjxwYXRoIGQ9Ik0xNiAxMnY2Ii8 + PHBhdGggZD0iTTYgMTJ2NiIvPjwvc3ZnPg ==
description : Provides deep reading analysis and summarization for long texts .
requirements : jinja2 , markdown
"""
from pydantic import BaseModel , Field
from typing import Optional , Dict , Any
import logging
import re
from fastapi import Request
from datetime import datetime
import pytz
import markdown
from jinja2 import Template
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__ )
# =================================================================
2025-12-20 14:27:37 +08:00
# Internal LLM Prompts
2025-12-20 12:34:49 +08:00
# =================================================================
SYSTEM_PROMPT_READING_ASSISTANT = """
2025-12-20 14:27:37 +08:00
You are a professional Deep Text Analysis Expert , specializing in reading long texts and extracting the essence . Your task is to conduct a comprehensive and in - depth analysis .
Please provide the following :
1. * * Detailed Summary * * : Summarize the core content of the text in 2 - 3 paragraphs , ensuring accuracy and completeness . Do not be too brief ; ensure the reader fully understands the main idea .
2. * * Key Information Points * * : List 5 - 8 most important facts , viewpoints , or arguments . Each point should :
- Be specific and insightful
- Include necessary details and context
- Use Markdown list format
3. * * Actionable Advice * * : Identify and refine specific , actionable items from the text . Each suggestion should :
- Be clear and actionable
- Include execution priority or timing suggestions
- If there are no clear action items , provide learning suggestions or thinking directions
Please strictly follow these guidelines :
- * * Language * * : All output must be in the user ' s specified language.
- * * Format * * : Please strictly follow the Markdown format below , ensuring each section has a clear header :
## Summary
[ Detailed summary content here , 2 - 3 paragraphs , use Markdown * * bold * * or * italic * to emphasize key points ]
## Key Information Points
- [ Key Point 1 : Include specific details and context ]
- [ Key Point 2 : Include specific details and context ]
- [ Key Point 3 : Include specific details and context ]
- [ At least 5 , at most 8 key points ]
## Actionable Advice
- [ Action Item 1 : Specific , actionable , include priority ]
- [ Action Item 2 : Specific , actionable , include priority ]
- [ If no clear action items , provide learning suggestions or thinking directions ]
- * * Depth First * * : Analysis should be deep and comprehensive , not superficial .
- * * Action Oriented * * : Focus on actionable suggestions and next steps .
- * * Analysis Results Only * * : Do not include any extra pleasantries , explanations , or leading text .
2025-12-20 12:34:49 +08:00
"""
USER_PROMPT_GENERATE_SUMMARY = """
2025-12-20 14:27:37 +08:00
Please conduct a deep analysis of the following long text , providing :
1. Detailed Summary ( 2 - 3 paragraphs , comprehensive overview )
2. Key Information Points List ( 5 - 8 items , including specific details )
3. Actionable Advice ( Specific , clear , including priority )
2025-12-20 12:34:49 +08:00
- - -
2025-12-20 14:27:37 +08:00
* * User Context : * *
User Name : { user_name }
Current Date / Time : { current_date_time_str }
Weekday : { current_weekday }
Timezone : { current_timezone_str }
User Language : { user_language }
2025-12-20 12:34:49 +08:00
- - -
2025-12-20 14:27:37 +08:00
* * Long Text Content : * *
2025-12-20 12:34:49 +08:00
` ` `
{ long_text_content }
` ` `
2025-12-20 14:27:37 +08:00
Please conduct a deep and comprehensive analysis , focusing on actionable advice .
2025-12-20 12:34:49 +08:00
"""
# =================================================================
2025-12-20 14:27:37 +08:00
# Frontend HTML Template (Jinja2 Syntax)
2025-12-20 12:34:49 +08:00
# =================================================================
HTML_TEMPLATE = """
< ! DOCTYPE html >
< html lang = " {{ user_language }} " >
< head >
< meta charset = " UTF-8 " >
< meta name = " viewport " content = " width=device-width, initial-scale=1.0 " >
2025-12-20 14:27:37 +08:00
< title > Deep Reading : Deep Analysis Report < / title >
2025-12-20 12:34:49 +08:00
< style >
: root {
- - primary - color : #4285f4;
- - secondary - color : #1e88e5;
- - action - color : #34a853;
- - background - color : #f8f9fa;
- - card - bg - color : #ffffff;
- - text - color : #202124;
- - muted - text - color : #5f6368;
- - border - color : #dadce0;
- - header - gradient : linear - gradient ( 135 deg , #4285f4, #1e88e5);
- - shadow : 0 1 px 3 px rgba ( 60 , 64 , 67 , .3 ) ;
- - border - radius : 8 px ;
- - font - family : - apple - system , BlinkMacSystemFont , " Segoe UI " , Roboto , " Helvetica Neue " , Arial , sans - serif ;
}
body {
font - family : var ( - - font - family ) ;
line - height : 1.8 ;
color : var ( - - text - color ) ;
margin : 0 ;
padding : 24 px ;
background - color : var ( - - background - color ) ;
- webkit - font - smoothing : antialiased ;
- moz - osx - font - smoothing : grayscale ;
}
. container {
max - width : 900 px ;
margin : 20 px auto ;
background : var ( - - card - bg - color ) ;
border - radius : var ( - - border - radius ) ;
box - shadow : var ( - - shadow ) ;
overflow : hidden ;
border : 1 px solid var ( - - border - color ) ;
}
. header {
background : var ( - - header - gradient ) ;
color : white ;
padding : 40 px ;
text - align : center ;
}
. header h1 {
margin : 0 ;
font - size : 2.2 em ;
font - weight : 500 ;
letter - spacing : - 0.5 px ;
}
. user - context {
font - size : 0.9 em ;
color : var ( - - muted - text - color ) ;
background - color : #f1f3f4;
padding : 16 px 40 px ;
display : flex ;
justify - content : space - around ;
flex - wrap : wrap ;
border - bottom : 1 px solid var ( - - border - color ) ;
}
. user - context span { margin : 4 px 12 px ; }
. content { padding : 40 px ; }
. section {
margin - bottom : 32 px ;
padding - bottom : 32 px ;
border - bottom : 1 px solid #e8eaed;
}
. section : last - child {
border - bottom : none ;
margin - bottom : 0 ;
padding - bottom : 0 ;
}
. section h2 {
margin - top : 0 ;
margin - bottom : 20 px ;
font - size : 1.5 em ;
font - weight : 500 ;
color : var ( - - text - color ) ;
display : flex ;
align - items : center ;
padding - bottom : 12 px ;
border - bottom : 2 px solid var ( - - primary - color ) ;
}
. section h2 . icon {
margin - right : 12 px ;
font - size : 1.3 em ;
line - height : 1 ;
}
. summary - section h2 { border - bottom - color : var ( - - primary - color ) ; }
. keypoints - section h2 { border - bottom - color : var ( - - secondary - color ) ; }
. actions - section h2 { border - bottom - color : var ( - - action - color ) ; }
. html - content {
font - size : 1.05 em ;
line - height : 1.8 ;
}
. html - content p : first - child { margin - top : 0 ; }
. html - content p : last - child { margin - bottom : 0 ; }
. html - content ul {
list - style : none ;
padding - left : 0 ;
margin : 16 px 0 ;
}
. html - content li {
padding : 12 px 0 12 px 32 px ;
position : relative ;
margin - bottom : 8 px ;
line - height : 1.7 ;
}
. html - content li : : before {
position : absolute ;
left : 0 ;
top : 12 px ;
font - family : ' Arial ' ;
font - weight : bold ;
font - size : 1.1 em ;
}
. keypoints - section . html - content li : : before {
content : ' • ' ;
color : var ( - - secondary - color ) ;
font - size : 1.5 em ;
top : 8 px ;
}
. actions - section . html - content li : : before {
content : ' ▸ ' ;
color : var ( - - action - color ) ;
}
. no - content {
color : var ( - - muted - text - color ) ;
font - style : italic ;
padding : 20 px ;
background : #f8f9fa;
border - radius : 4 px ;
}
. footer {
text - align : center ;
padding : 24 px ;
font - size : 0.85 em ;
color : #5f6368;
background - color : #f8f9fa;
border - top : 1 px solid var ( - - border - color ) ;
}
< / style >
< / head >
< body >
< div class = " container " >
< div class = " header " >
2025-12-20 14:27:37 +08:00
< h1 > 📖 Deep Reading : Deep Analysis Report < / h1 >
2025-12-20 12:34:49 +08:00
< / div >
< div class = " user-context " >
2025-12-20 14:27:37 +08:00
< span > < strong > User : < / strong > { { user_name } } < / span >
< span > < strong > Analysis Time : < / strong > { { current_date_time_str } } < / span >
< span > < strong > Weekday : < / strong > { { current_weekday } } < / span >
2025-12-20 12:34:49 +08:00
< / div >
< div class = " content " >
< div class = " section summary-section " >
2025-12-20 14:27:37 +08:00
< h2 > < span class = " icon " > 📝 < / span > Detailed Summary < / h2 >
2025-12-20 12:34:49 +08:00
< div class = " html-content " > { { summary_html | safe } } < / div >
< / div >
< div class = " section keypoints-section " >
2025-12-20 14:27:37 +08:00
< h2 > < span class = " icon " > 💡 < / span > Key Information Points < / h2 >
2025-12-20 12:34:49 +08:00
< div class = " html-content " > { { keypoints_html | safe } } < / div >
< / div >
< div class = " section actions-section " >
2025-12-20 14:27:37 +08:00
< h2 > < span class = " icon " > 🎯 < / span > Actionable Advice < / h2 >
2025-12-20 12:34:49 +08:00
< div class = " html-content " > { { actions_html | safe } } < / div >
< / div >
< / div >
< div class = " footer " >
2025-12-20 14:27:37 +08:00
< p > & copy ; { { current_year } } Deep Reading - Deep Text Analysis Service < / p >
2025-12-20 12:34:49 +08:00
< / div >
< / div >
< / body >
< / html > """
class Action :
class Valves ( BaseModel ) :
show_status : bool = Field (
2025-12-20 14:27:37 +08:00
default = True ,
description = " Whether to show operation status updates in the chat interface. " ,
2025-12-20 12:34:49 +08:00
)
LLM_MODEL_ID : str = Field (
default = " gemini-2.5-flash " ,
2025-12-20 14:27:37 +08:00
description = " Built-in LLM Model ID used for text analysis. " ,
2025-12-20 12:34:49 +08:00
)
MIN_TEXT_LENGTH : int = Field (
default = 200 ,
2025-12-20 14:27:37 +08:00
description = " Minimum text length required for deep analysis (characters). Recommended 200+. " ,
2025-12-20 12:34:49 +08:00
)
RECOMMENDED_MIN_LENGTH : int = Field (
2025-12-20 14:27:37 +08:00
default = 500 ,
description = " Recommended minimum text length for best analysis results. " ,
2025-12-20 12:34:49 +08:00
)
def __init__ ( self ) :
self . valves = self . Valves ( )
def _process_llm_output ( self , llm_output : str ) - > Dict [ str , str ] :
"""
2025-12-20 14:27:37 +08:00
Parse LLM Markdown output and convert to HTML fragments .
2025-12-20 12:34:49 +08:00
"""
summary_match = re . search (
2025-12-20 14:27:37 +08:00
r " ## \ s*Summary \ s* \ n(.*?)(?= \ n##|$) " , llm_output , re . DOTALL | re . IGNORECASE
2025-12-20 12:34:49 +08:00
)
keypoints_match = re . search (
2025-12-20 14:27:37 +08:00
r " ## \ s*Key Information Points \ s* \ n(.*?)(?= \ n##|$) " ,
llm_output ,
re . DOTALL | re . IGNORECASE ,
2025-12-20 12:34:49 +08:00
)
actions_match = re . search (
2025-12-20 14:27:37 +08:00
r " ## \ s*Actionable Advice \ s* \ n(.*?)(?= \ n##|$) " ,
llm_output ,
re . DOTALL | re . IGNORECASE ,
2025-12-20 12:34:49 +08:00
)
summary_md = summary_match . group ( 1 ) . strip ( ) if summary_match else " "
keypoints_md = keypoints_match . group ( 1 ) . strip ( ) if keypoints_match else " "
actions_md = actions_match . group ( 1 ) . strip ( ) if actions_match else " "
if not any ( [ summary_md , keypoints_md , actions_md ] ) :
summary_md = llm_output . strip ( )
2025-12-20 14:27:37 +08:00
logger . warning (
" LLM output did not follow expected Markdown format. Treating entire output as summary. "
)
2025-12-20 12:34:49 +08:00
2025-12-20 14:27:37 +08:00
# Use 'nl2br' extension to convert newlines \n to <br>
2025-12-20 12:34:49 +08:00
md_extensions = [ " nl2br " ]
summary_html = (
markdown . markdown ( summary_md , extensions = md_extensions )
if summary_md
2025-12-20 14:27:37 +08:00
else ' <p class= " no-content " >Failed to extract summary.</p> '
2025-12-20 12:34:49 +08:00
)
keypoints_html = (
markdown . markdown ( keypoints_md , extensions = md_extensions )
if keypoints_md
2025-12-20 14:27:37 +08:00
else ' <p class= " no-content " >Failed to extract key information points.</p> '
2025-12-20 12:34:49 +08:00
)
actions_html = (
markdown . markdown ( actions_md , extensions = md_extensions )
if actions_md
2025-12-20 14:27:37 +08:00
else ' <p class= " no-content " >No explicit actionable advice.</p> '
2025-12-20 12:34:49 +08:00
)
return {
" summary_html " : summary_html ,
" keypoints_html " : keypoints_html ,
" actions_html " : actions_html ,
}
def _build_html ( self , context : dict ) - > str :
"""
2025-12-20 14:27:37 +08:00
Build final HTML content using Jinja2 template and context data .
2025-12-20 12:34:49 +08:00
"""
template = Template ( HTML_TEMPLATE )
return template . render ( context )
async def action (
self ,
body : dict ,
__user__ : Optional [ Dict [ str , Any ] ] = None ,
__event_emitter__ : Optional [ Any ] = None ,
__request__ : Optional [ Request ] = None ,
) - > Optional [ dict ] :
2025-12-20 14:27:37 +08:00
logger . info ( " Action: Deep Reading Started (v2.0.0) " )
2025-12-20 12:34:49 +08:00
if isinstance ( __user__ , ( list , tuple ) ) :
user_language = (
2025-12-20 14:27:37 +08:00
__user__ [ 0 ] . get ( " language " , " en-US " ) if __user__ else " en-US "
2025-12-20 12:34:49 +08:00
)
2025-12-20 14:27:37 +08:00
user_name = __user__ [ 0 ] . get ( " name " , " User " ) if __user__ [ 0 ] else " User "
2025-12-20 12:34:49 +08:00
user_id = (
__user__ [ 0 ] [ " id " ]
if __user__ and " id " in __user__ [ 0 ]
else " unknown_user "
)
elif isinstance ( __user__ , dict ) :
2025-12-20 14:27:37 +08:00
user_language = __user__ . get ( " language " , " en-US " )
user_name = __user__ . get ( " name " , " User " )
2025-12-20 12:34:49 +08:00
user_id = __user__ . get ( " id " , " unknown_user " )
now = datetime . now ( )
current_date_time_str = now . strftime ( " % Y- % m- %d % H: % M: % S " )
current_weekday = now . strftime ( " % A " )
current_year = now . strftime ( " % Y " )
2025-12-20 14:27:37 +08:00
current_timezone_str = " Unknown Timezone "
2025-12-20 12:34:49 +08:00
original_content = " "
try :
messages = body . get ( " messages " , [ ] )
if not messages or not messages [ - 1 ] . get ( " content " ) :
2025-12-20 14:27:37 +08:00
raise ValueError ( " Unable to get valid user message content. " )
2025-12-20 12:34:49 +08:00
original_content = messages [ - 1 ] [ " content " ]
if len ( original_content ) < self . valves . MIN_TEXT_LENGTH :
2025-12-20 14:27:37 +08:00
short_text_message = f " Text content too short ( { len ( original_content ) } chars), recommended at least { self . valves . MIN_TEXT_LENGTH } chars for effective deep analysis. \n \n 💡 Tip: For short texts, consider using ' ⚡ Flash Card ' for quick refinement. "
2025-12-20 12:34:49 +08:00
if __event_emitter__ :
await __event_emitter__ (
{
" type " : " notification " ,
" data " : { " type " : " warning " , " content " : short_text_message } ,
}
)
return {
" messages " : [
{ " role " : " assistant " , " content " : f " ⚠️ { short_text_message } " }
]
}
# Recommend for longer texts
if len ( original_content ) < self . valves . RECOMMENDED_MIN_LENGTH :
if __event_emitter__ :
await __event_emitter__ (
{
" type " : " notification " ,
" data " : {
" type " : " info " ,
2025-12-20 14:27:37 +08:00
" content " : f " Text length is { len ( original_content ) } chars. Recommended { self . valves . RECOMMENDED_MIN_LENGTH } + chars for best analysis results. " ,
2025-12-20 12:34:49 +08:00
} ,
}
)
if __event_emitter__ :
await __event_emitter__ (
{
" type " : " notification " ,
" data " : {
" type " : " info " ,
2025-12-20 14:27:37 +08:00
" content " : " 📖 Deep Reading started, analyzing deeply... " ,
2025-12-20 12:34:49 +08:00
} ,
}
)
if self . valves . show_status :
await __event_emitter__ (
{
" type " : " status " ,
" data " : {
2025-12-20 14:27:37 +08:00
" description " : " 📖 Deep Reading: Analyzing text, extracting essence... " ,
2025-12-20 12:34:49 +08:00
" done " : False ,
} ,
}
)
formatted_user_prompt = USER_PROMPT_GENERATE_SUMMARY . format (
user_name = user_name ,
current_date_time_str = current_date_time_str ,
current_weekday = current_weekday ,
current_timezone_str = current_timezone_str ,
user_language = user_language ,
long_text_content = original_content ,
)
llm_payload = {
" model " : self . valves . LLM_MODEL_ID ,
" messages " : [
{ " role " : " system " , " content " : SYSTEM_PROMPT_READING_ASSISTANT } ,
{ " role " : " user " , " content " : formatted_user_prompt } ,
] ,
" stream " : False ,
}
user_obj = Users . get_user_by_id ( user_id )
if not user_obj :
2025-12-20 14:27:37 +08:00
raise ValueError ( f " Unable to get user object, User ID: { user_id } " )
2025-12-20 12:34:49 +08:00
llm_response = await generate_chat_completion (
__request__ , llm_payload , user_obj
)
assistant_response_content = llm_response [ " choices " ] [ 0 ] [ " message " ] [
" content "
]
processed_content = self . _process_llm_output ( assistant_response_content )
context = {
" user_language " : user_language ,
" user_name " : user_name ,
" current_date_time_str " : current_date_time_str ,
" current_weekday " : current_weekday ,
" current_year " : current_year ,
* * processed_content ,
}
final_html_content = self . _build_html ( context )
html_embed_tag = f " ```html \n { final_html_content } \n ``` "
body [ " messages " ] [ - 1 ] [ " content " ] = f " { original_content } \n \n { html_embed_tag } "
if self . valves . show_status and __event_emitter__ :
await __event_emitter__ (
{
" type " : " status " ,
2025-12-20 14:27:37 +08:00
" data " : {
" description " : " 📖 Deep Reading: Analysis complete! " ,
" done " : True ,
} ,
2025-12-20 12:34:49 +08:00
}
)
await __event_emitter__ (
{
" type " : " notification " ,
" data " : {
" type " : " success " ,
2025-12-20 14:27:37 +08:00
" content " : f " 📖 Deep Reading complete, { user_name } ! Deep analysis report generated. " ,
2025-12-20 12:34:49 +08:00
} ,
}
)
except Exception as e :
2025-12-20 14:27:37 +08:00
error_message = f " Deep Reading processing failed: { str ( e ) } "
logger . error ( f " Deep Reading Error: { error_message } " , exc_info = True )
user_facing_error = f " Sorry, Deep Reading encountered an error while processing: { str ( e ) } . \n Please check Open WebUI backend logs for more details. "
2025-12-20 12:34:49 +08:00
body [ " messages " ] [ - 1 ] [
" content "
2025-12-20 14:27:37 +08:00
] = f " { original_content } \n \n ❌ **Error:** { user_facing_error } "
2025-12-20 12:34:49 +08:00
if __event_emitter__ :
if self . valves . show_status :
await __event_emitter__ (
{
" type " : " status " ,
" data " : {
2025-12-20 14:27:37 +08:00
" description " : " Deep Reading: Processing failed. " ,
2025-12-20 12:34:49 +08:00
" done " : True ,
} ,
}
)
await __event_emitter__ (
{
" type " : " notification " ,
" data " : {
" type " : " error " ,
2025-12-20 14:27:37 +08:00
" content " : f " Deep Reading processing failed, { user_name } ! " ,
2025-12-20 12:34:49 +08:00
} ,
}
)
return body