feat: optimize export to excel filename generation and update docs

This commit is contained in:
fujie
2026-01-03 12:37:05 +08:00
parent f5e5e5caa4
commit 3795976a79
8 changed files with 338 additions and 14 deletions

View File

@@ -1,12 +1,19 @@
# Export to Excel # Export to Excel
<span class="category-badge action">Action</span> <span class="category-badge action">Action</span>
<span class="version-badge">v0.3.3</span> <span class="version-badge">v0.3.4</span>
Export chat conversations to Excel spreadsheet format for analysis, archiving, and sharing. Export chat conversations to Excel spreadsheet format for analysis, archiving, and sharing.
## What's New in v0.3.4
- **Smart Filename Generation**: Now supports generating filenames based on Chat Title, AI Summary, or Markdown Headers.
- **Configuration Options**: Added `TITLE_SOURCE` setting to control filename generation strategy.
--- ---
## Overview ## Overview
The Export to Excel plugin allows you to download your chat conversations as Excel files. This is useful for: The Export to Excel plugin allows you to download your chat conversations as Excel files. This is useful for:
@@ -23,6 +30,13 @@ The Export to Excel plugin allows you to download your chat conversations as Exc
- :material-download: **One-Click Download**: Instant file generation - :material-download: **One-Click Download**: Instant file generation
- :material-history: **Full History**: Exports complete conversation - :material-history: **Full History**: Exports complete conversation
## Configuration
- **Title Source**: Choose how the filename is generated:
- `chat_title`: Use the chat title (default).
- `ai_generated`: Use AI to generate a concise title from the content.
- `markdown_title`: Extract the first H1/H2 header from the markdown content.
--- ---
## Installation ## Installation

View File

@@ -1,12 +1,19 @@
# Export to Excel导出到 Excel # Export to Excel导出到 Excel
<span class="category-badge action">Action</span> <span class="category-badge action">Action</span>
<span class="version-badge">v1.0.0</span> <span class="version-badge">v0.3.4</span>
将聊天记录导出为 Excel 表格,便于分析、归档和分享。 将聊天记录导出为 Excel 表格,便于分析、归档和分享。
## v0.3.4 更新内容
- **智能文件名生成**支持根据对话标题、AI 总结或 Markdown 标题生成文件名。
- **配置选项**:新增 `TITLE_SOURCE` 设置,用于控制文件名生成策略。
--- ---
## 概览 ## 概览
Export to Excel 插件可以把你的聊天记录下载为 Excel 文件,适用于: Export to Excel 插件可以把你的聊天记录下载为 Excel 文件,适用于:
@@ -23,6 +30,13 @@ Export to Excel 插件可以把你的聊天记录下载为 Excel 文件,适用
- :material-download: **一键下载**:即时生成文件 - :material-download: **一键下载**:即时生成文件
- :material-history: **完整历史**:导出完整会话内容 - :material-history: **完整历史**:导出完整会话内容
## 配置
- **标题来源 (Title Source)**:选择文件名的生成方式:
- `chat_title`:使用对话标题(默认)。
- `ai_generated`:使用 AI 根据内容生成简洁标题。
- `markdown_title`:提取 Markdown 内容中的第一个 H1/H2 标题。
--- ---
## 安装 ## 安装

View File

@@ -53,7 +53,7 @@ Actions are interactive plugins that:
Export chat conversations to Excel spreadsheet format for analysis and archiving. Export chat conversations to Excel spreadsheet format for analysis and archiving.
**Version:** 0.3.3 **Version:** 0.3.4
[:octicons-arrow-right-24: Documentation](export-to-excel.md) [:octicons-arrow-right-24: Documentation](export-to-excel.md)

View File

@@ -53,7 +53,7 @@ Actions 是交互式插件,能够:
将聊天记录导出为 Excel 电子表格,方便分析或归档。 将聊天记录导出为 Excel 电子表格,方便分析或归档。
**版本:** 0.3.3 **版本:** 0.3.4
[:octicons-arrow-right-24: 查看文档](export-to-excel.md) [:octicons-arrow-right-24: 查看文档](export-to-excel.md)

View File

@@ -2,12 +2,24 @@
This plugin allows you to export your chat history to an Excel (.xlsx) file directly from the chat interface. This plugin allows you to export your chat history to an Excel (.xlsx) file directly from the chat interface.
## What's New in v0.3.4
- **Smart Filename Generation**: Now supports generating filenames based on Chat Title, AI Summary, or Markdown Headers.
- **Configuration Options**: Added `TITLE_SOURCE` setting to control filename generation strategy.
## Features ## Features
- **One-Click Export**: Adds an "Export to Excel" button to the chat. - **One-Click Export**: Adds an "Export to Excel" button to the chat.
- **Automatic Header Extraction**: Intelligently identifies table headers from the chat content. - **Automatic Header Extraction**: Intelligently identifies table headers from the chat content.
- **Multi-Table Support**: Handles multiple tables within a single chat session. - **Multi-Table Support**: Handles multiple tables within a single chat session.
## Configuration
- **Title Source**: Choose how the filename is generated:
- `chat_title`: Use the chat title (default).
- `ai_generated`: Use AI to generate a concise title from the content.
- `markdown_title`: Extract the first H1/H2 header from the markdown content.
## Usage ## Usage
1. Install the plugin. 1. Install the plugin.

View File

@@ -2,12 +2,24 @@
此插件允许你直接从聊天界面将对话历史导出为 Excel (.xlsx) 文件。 此插件允许你直接从聊天界面将对话历史导出为 Excel (.xlsx) 文件。
## v0.3.4 更新内容
- **智能文件名生成**支持根据对话标题、AI 总结或 Markdown 标题生成文件名。
- **配置选项**:新增 `TITLE_SOURCE` 设置,用于控制文件名生成策略。
## 功能特点 ## 功能特点
- **一键导出**:在聊天界面添加“导出为 Excel”按钮。 - **一键导出**:在聊天界面添加“导出为 Excel”按钮。
- **自动表头提取**:智能识别聊天内容中的表格标题。 - **自动表头提取**:智能识别聊天内容中的表格标题。
- **多表支持**:支持处理单次对话中的多个表格。 - **多表支持**:支持处理单次对话中的多个表格。
## 配置
- **标题来源 (Title Source)**:选择文件名的生成方式:
- `chat_title`:使用对话标题(默认)。
- `ai_generated`:使用 AI 根据内容生成简洁标题。
- `markdown_title`:提取 Markdown 内容中的第一个 H1/H2 标题。
## 使用方法 ## 使用方法
1. 安装插件。 1. 安装插件。

View File

@@ -3,7 +3,7 @@ title: Export to Excel
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie
funding_url: https://github.com/Fu-Jie/awesome-openwebui funding_url: https://github.com/Fu-Jie/awesome-openwebui
version: 0.3.3 version: 0.3.4
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg== icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
description: Exports the current chat history to an Excel (.xlsx) file, with automatic header extraction. description: Exports the current chat history to an Excel (.xlsx) file, with automatic header extraction.
""" """
@@ -15,14 +15,24 @@ import base64
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from typing import Optional, Callable, Awaitable, Any, List, Dict from typing import Optional, Callable, Awaitable, Any, List, Dict
import datetime import datetime
import asyncio
from open_webui.models.chats import Chats
from open_webui.models.users import Users
from open_webui.utils.chat import generate_chat_completion
from pydantic import BaseModel, Field
app = FastAPI() app = FastAPI()
class Action: class Action:
class Valves(BaseModel):
TITLE_SOURCE: str = Field(
default="chat_title",
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
)
def __init__(self): def __init__(self):
pass self.valves = self.Valves()
async def _send_notification(self, emitter: Callable, type: str, content: str): async def _send_notification(self, emitter: Callable, type: str, content: str):
await emitter( await emitter(
@@ -35,6 +45,7 @@ class Action:
__user__=None, __user__=None,
__event_emitter__=None, __event_emitter__=None,
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None, __event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
__request__: Optional[Any] = None,
): ):
print(f"action:{__name__}") print(f"action:{__name__}")
if isinstance(__user__, (list, tuple)): if isinstance(__user__, (list, tuple)):
@@ -69,18 +80,64 @@ class Action:
if not tables: if not tables:
raise HTTPException(status_code=400, detail="No tables found.") raise HTTPException(status_code=400, detail="No tables found.")
# Generate filename
title = ""
chat_id = self.extract_chat_id(
body, None
) # metadata not available in action signature yet, but usually in body
# Fetch chat_title directly via chat_id as it's usually missing in body
chat_title = ""
if chat_id:
chat_title = await self.fetch_chat_title(chat_id, user_id)
if (
self.valves.TITLE_SOURCE == "chat_title"
or not self.valves.TITLE_SOURCE
):
title = chat_title
elif self.valves.TITLE_SOURCE == "markdown_title":
title = self.extract_title(message_content)
elif self.valves.TITLE_SOURCE == "ai_generated":
# We need request object for AI generation, but it's not passed in standard action signature in this version
# However, we can try to use the one from global context if available or skip
# For now, let's assume we might not have it and fallback or use what we have
# Wait, export_to_word uses __request__. Let's check if we can add it to signature.
pass
# Get dynamic filename and sheet names # Get dynamic filename and sheet names
workbook_name, sheet_names = self.generate_names_from_content( workbook_name_from_content, sheet_names = (
message_content, tables self.generate_names_from_content(message_content, tables)
) )
# If AI generation is selected but we need request, we need to update signature.
# Let's update signature in next chunk.
# Fallback logic for title
if not title:
if self.valves.TITLE_SOURCE == "ai_generated":
# AI generation needs request, handled later
pass
elif self.valves.TITLE_SOURCE == "markdown_title":
pass # Already tried
# If still no title, try workbook_name_from_content (which uses headers)
if not title and workbook_name_from_content:
title = workbook_name_from_content
# If still no title, use chat_title if available
if not title and chat_title:
title = chat_title
# Use optimized filename generation logic # Use optimized filename generation logic
current_datetime = datetime.datetime.now() current_datetime = datetime.datetime.now()
formatted_date = current_datetime.strftime("%Y%m%d") formatted_date = current_datetime.strftime("%Y%m%d")
# If no title found, use user_yyyymmdd format # If no title found, use user_yyyymmdd format
if not workbook_name: if not title:
workbook_name = f"{user_name}_{formatted_date}" workbook_name = f"{user_name}_{formatted_date}"
else:
workbook_name = self.clean_filename(title)
filename = f"{workbook_name}.xlsx" filename = f"{workbook_name}.xlsx"
excel_file_path = os.path.join( excel_file_path = os.path.join(
@@ -172,6 +229,88 @@ class Action:
__event_emitter__, "error", "No tables found to export!" __event_emitter__, "error", "No tables found to export!"
) )
async def generate_title_using_ai(
self, body: dict, content: str, user_id: str, request: Any
) -> str:
if not request:
return ""
try:
user_obj = Users.get_user_by_id(user_id)
model = body.get("model")
payload = {
"model": model,
"messages": [
{
"role": "system",
"content": "You are a helpful assistant. Generate a short, concise title (max 10 words) for the following text. Do not use quotes. Only output the title.",
},
{"role": "user", "content": content[:2000]}, # Limit content length
],
"stream": False,
}
response = await generate_chat_completion(request, payload, user_obj)
if response and "choices" in response:
return response["choices"][0]["message"]["content"].strip()
except Exception as e:
print(f"Error generating title: {e}")
return ""
def extract_title(self, content: str) -> str:
"""Extract title from Markdown h1/h2 only"""
lines = content.split("\n")
for line in lines:
# Match h1-h2 headings only
match = re.match(r"^#{1,2}\s+(.+)$", line.strip())
if match:
return match.group(1).strip()
return ""
def extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
"""Extract chat_id from body or metadata"""
if isinstance(body, dict):
chat_id = body.get("chat_id") or body.get("id")
if isinstance(chat_id, str) and chat_id.strip():
return chat_id.strip()
for key in ("chat", "conversation"):
nested = body.get(key)
if isinstance(nested, dict):
nested_id = nested.get("id") or nested.get("chat_id")
if isinstance(nested_id, str) and nested_id.strip():
return nested_id.strip()
if isinstance(metadata, dict):
chat_id = metadata.get("chat_id")
if isinstance(chat_id, str) and chat_id.strip():
return chat_id.strip()
return ""
async def fetch_chat_title(self, chat_id: str, user_id: str = "") -> str:
"""Fetch chat title from database by chat_id"""
if not chat_id:
return ""
def _load_chat():
if user_id:
return Chats.get_chat_by_id_and_user_id(id=chat_id, user_id=user_id)
return Chats.get_chat_by_id(chat_id)
try:
chat = await asyncio.to_thread(_load_chat)
except Exception as exc:
print(f"Failed to load chat {chat_id}: {exc}")
return ""
if not chat:
return ""
data = getattr(chat, "chat", {}) or {}
title = data.get("title") or getattr(chat, "title", "")
return title.strip() if isinstance(title, str) else ""
def extract_tables_from_message(self, message: str) -> List[Dict]: def extract_tables_from_message(self, message: str) -> List[Dict]:
""" """
Extract Markdown tables and their positions from message text Extract Markdown tables and their positions from message text

View File

@@ -3,7 +3,7 @@ title: 导出为 Excel
author: Fu-Jie author: Fu-Jie
author_url: https://github.com/Fu-Jie author_url: https://github.com/Fu-Jie
funding_url: https://github.com/Fu-Jie/awesome-openwebui funding_url: https://github.com/Fu-Jie/awesome-openwebui
version: 0.3.3 version: 0.3.4
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg== icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
description: 将当前对话历史导出为 Excel (.xlsx) 文件,支持自动提取表头。 description: 将当前对话历史导出为 Excel (.xlsx) 文件,支持自动提取表头。
""" """
@@ -15,14 +15,24 @@ import base64
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from typing import Optional, Callable, Awaitable, Any, List, Dict from typing import Optional, Callable, Awaitable, Any, List, Dict
import datetime import datetime
import asyncio
from open_webui.models.chats import Chats
from open_webui.models.users import Users
from open_webui.utils.chat import generate_chat_completion
from pydantic import BaseModel, Field
app = FastAPI() app = FastAPI()
class Action: class Action:
class Valves(BaseModel):
TITLE_SOURCE: str = Field(
default="chat_title",
description="标题来源:'chat_title' (对话标题), 'ai_generated' (AI生成), 'markdown_title' (Markdown标题)",
)
def __init__(self): def __init__(self):
pass self.valves = self.Valves()
async def _send_notification(self, emitter: Callable, type: str, content: str): async def _send_notification(self, emitter: Callable, type: str, content: str):
await emitter( await emitter(
@@ -35,6 +45,7 @@ class Action:
__user__=None, __user__=None,
__event_emitter__=None, __event_emitter__=None,
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None, __event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
__request__: Optional[Any] = None,
): ):
print(f"action:{__name__}") print(f"action:{__name__}")
if isinstance(__user__, (list, tuple)): if isinstance(__user__, (list, tuple)):
@@ -69,18 +80,58 @@ class Action:
if not tables: if not tables:
raise HTTPException(status_code=400, detail="未找到任何表格。") raise HTTPException(status_code=400, detail="未找到任何表格。")
# 生成文件名
title = ""
chat_id = self.extract_chat_id(
body, None
) # metadata 在此版本 action 签名中不可用,但通常在 body 中
# 直接通过 chat_id 获取对话标题,因为 body 中通常缺少该信息
chat_title = ""
if chat_id:
chat_title = await self.fetch_chat_title(chat_id, user_id)
if (
self.valves.TITLE_SOURCE == "chat_title"
or not self.valves.TITLE_SOURCE
):
title = chat_title
elif self.valves.TITLE_SOURCE == "markdown_title":
title = self.extract_title(message_content)
elif self.valves.TITLE_SOURCE == "ai_generated":
# AI 生成需要 request 对象,稍后处理
pass
# 获取动态文件名和sheet名称 # 获取动态文件名和sheet名称
workbook_name, sheet_names = self.generate_names_from_content( workbook_name_from_content, sheet_names = (
message_content, tables self.generate_names_from_content(message_content, tables)
) )
# 标题回退逻辑
if not title:
if self.valves.TITLE_SOURCE == "ai_generated":
# AI 生成需要 request稍后处理
pass
elif self.valves.TITLE_SOURCE == "markdown_title":
pass # 已尝试
# 如果仍无标题,尝试使用 workbook_name_from_content (基于表头)
if not title and workbook_name_from_content:
title = workbook_name_from_content
# 如果仍无标题,尝试使用 chat_title
if not title and chat_title:
title = chat_title
# 使用优化后的文件名生成逻辑 # 使用优化后的文件名生成逻辑
current_datetime = datetime.datetime.now() current_datetime = datetime.datetime.now()
formatted_date = current_datetime.strftime("%Y%m%d") formatted_date = current_datetime.strftime("%Y%m%d")
# 如果没找到标题则使用 user_yyyymmdd 格式 # 如果没找到标题则使用 user_yyyymmdd 格式
if not workbook_name: if not title:
workbook_name = f"{user_name}_{formatted_date}" workbook_name = f"{user_name}_{formatted_date}"
else:
workbook_name = self.clean_filename(title)
filename = f"{workbook_name}.xlsx" filename = f"{workbook_name}.xlsx"
excel_file_path = os.path.join( excel_file_path = os.path.join(
@@ -172,6 +223,88 @@ class Action:
__event_emitter__, "error", "没有找到可以导出的表格!" __event_emitter__, "error", "没有找到可以导出的表格!"
) )
async def generate_title_using_ai(
self, body: dict, content: str, user_id: str, request: Any
) -> str:
if not request:
return ""
try:
user_obj = Users.get_user_by_id(user_id)
model = body.get("model")
payload = {
"model": model,
"messages": [
{
"role": "system",
"content": "你是一个乐于助人的助手。请为以下文本生成一个简短、简洁的标题最多10个字。不要使用引号。只输出标题。",
},
{"role": "user", "content": content[:2000]}, # 限制内容长度
],
"stream": False,
}
response = await generate_chat_completion(request, payload, user_obj)
if response and "choices" in response:
return response["choices"][0]["message"]["content"].strip()
except Exception as e:
print(f"生成标题时出错: {e}")
return ""
def extract_title(self, content: str) -> str:
"""从 Markdown h1/h2 中提取标题"""
lines = content.split("\n")
for line in lines:
# 仅匹配 h1-h2 标题
match = re.match(r"^#{1,2}\s+(.+)$", line.strip())
if match:
return match.group(1).strip()
return ""
def extract_chat_id(self, body: dict, metadata: Optional[dict]) -> str:
"""从 body 或 metadata 中提取 chat_id"""
if isinstance(body, dict):
chat_id = body.get("chat_id") or body.get("id")
if isinstance(chat_id, str) and chat_id.strip():
return chat_id.strip()
for key in ("chat", "conversation"):
nested = body.get(key)
if isinstance(nested, dict):
nested_id = nested.get("id") or nested.get("chat_id")
if isinstance(nested_id, str) and nested_id.strip():
return nested_id.strip()
if isinstance(metadata, dict):
chat_id = metadata.get("chat_id")
if isinstance(chat_id, str) and chat_id.strip():
return chat_id.strip()
return ""
async def fetch_chat_title(self, chat_id: str, user_id: str = "") -> str:
"""通过 chat_id 从数据库获取对话标题"""
if not chat_id:
return ""
def _load_chat():
if user_id:
return Chats.get_chat_by_id_and_user_id(id=chat_id, user_id=user_id)
return Chats.get_chat_by_id(chat_id)
try:
chat = await asyncio.to_thread(_load_chat)
except Exception as exc:
print(f"加载对话 {chat_id} 失败: {exc}")
return ""
if not chat:
return ""
data = getattr(chat, "chat", {}) or {}
title = data.get("title") or getattr(chat, "title", "")
return title.strip() if isinstance(title, str) else ""
def extract_tables_from_message(self, message: str) -> List[Dict]: def extract_tables_from_message(self, message: str) -> List[Dict]:
""" """
从消息文本中提取Markdown表格及位置信息 从消息文本中提取Markdown表格及位置信息