Compare commits

...

14 Commits

Author SHA1 Message Date
fujie
dbfce27986 fix: explicitly add newlines before EOF in release workflow 2026-01-03 16:04:21 +08:00
fujie
9be6fe08fa fix: ensure extract_plugin_versions.py output ends with newline to prevent GH Actions EOF error 2026-01-03 16:00:50 +08:00
fujie
782378eed8 fix: quote GITHUB_OUTPUT delimiter to prevent EOF error 2026-01-03 14:18:50 +08:00
fujie
4e59bb6518 feat: support full-cell markdown italic formatting in excel export 2026-01-03 14:18:32 +08:00
fujie
3e73fcb3f0 feat: refine excel export to apply bold formatting only to fully bolded cells 2026-01-03 14:16:00 +08:00
fujie
c460337c43 feat: support markdown italic formatting and refine bold parsing 2026-01-03 14:12:53 +08:00
fujie
e775b23503 feat: support markdown bold formatting in excel export 2026-01-03 14:10:11 +08:00
fujie
b3cdb8e26e fix: use gh cli for asset upload to support chinese filenames 2026-01-03 13:52:25 +08:00
fujie
0e6f902d16 chore: add debug steps and artifact upload to release workflow 2026-01-03 13:38:59 +08:00
fujie
c15c73897f fix: enforce utf-8 and disable git path quoting in release workflow to support chinese filenames 2026-01-03 13:25:15 +08:00
fujie
035439ce02 docs: forbid agents from auto-pushing to remote main branch 2026-01-03 13:21:31 +08:00
fujie
b84ff4a3a2 chore: disable auto-release on push to main, use workflow_dispatch only 2026-01-03 13:18:36 +08:00
fujie
e22744abd0 feat: add export scope option and smart sheet naming to export to excel plugin (v0.3.5) 2026-01-03 13:15:13 +08:00
fujie
54c90238f7 fix: enforce utf-8 locale in release workflow to support chinese filenames 2026-01-03 12:42:26 +08:00
10 changed files with 442 additions and 105 deletions

View File

@@ -91,3 +91,12 @@ Before committing:
- [ ] `docs/` index and detail pages are updated?
- [ ] Root `README.md` is updated?
- [ ] All version numbers match exactly?
## 5. Git Operations (Agent Rules)
**CRITICAL RULE FOR AGENTS**:
- **No Auto-Push**: Agents **MUST NOT** automatically push changes to the remote `main` branch.
- **Local Commit Only**: All changes must be committed locally.
- **User Approval**: Pushing to remote requires explicit user action or approval.

View File

@@ -54,6 +54,9 @@ permissions:
jobs:
check-changes:
runs-on: ubuntu-latest
env:
LANG: en_US.UTF-8
LC_ALL: en_US.UTF-8
outputs:
has_changes: ${{ steps.detect.outputs.has_changes }}
changed_plugins: ${{ steps.detect.outputs.changed_plugins }}
@@ -65,6 +68,12 @@ jobs:
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global core.quotepath false
git config --global i18n.commitencoding utf-8
git config --global i18n.logoutputencoding utf-8
- name: Set up Python
uses: actions/setup-python@v5
with:
@@ -131,6 +140,7 @@ jobs:
echo "changed_plugins<<EOF" >> $GITHUB_OUTPUT
cat changed_files.txt >> $GITHUB_OUTPUT
echo "" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
fi
@@ -138,6 +148,7 @@ jobs:
{
echo 'release_notes<<EOF'
cat changes.md
echo ""
echo 'EOF'
} >> $GITHUB_OUTPUT
@@ -145,6 +156,10 @@ jobs:
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true' || github.event_name == 'workflow_dispatch' || startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
env:
LANG: en_US.UTF-8
LC_ALL: en_US.UTF-8
steps:
- name: Checkout repository
@@ -152,6 +167,12 @@ jobs:
with:
fetch-depth: 0
- name: Configure Git
run: |
git config --global core.quotepath false
git config --global i18n.commitencoding utf-8
git config --global i18n.logoutputencoding utf-8
- name: Set up Python
uses: actions/setup-python@v5
with:
@@ -205,6 +226,17 @@ jobs:
echo "=== Collected Files ==="
find release_plugins -name "*.py" -type f | head -20
- name: Debug Filenames
run: |
python3 -c "import sys; print(f'Filesystem encoding: {sys.getfilesystemencoding()}')"
ls -R release_plugins
- name: Upload Debug Artifacts
uses: actions/upload-artifact@v4
with:
name: debug-plugins
path: release_plugins/
- name: Get commit messages
id: commits
if: github.event_name == 'push'
@@ -220,8 +252,9 @@ jobs:
{
echo 'commits<<EOF'
echo "$COMMITS"
echo ""
echo 'EOF'
} >> $GITHUB_OUTPUT
} >> "$GITHUB_OUTPUT"
- name: Generate release notes
id: notes
@@ -301,10 +334,15 @@ jobs:
prerelease: ${{ github.event.inputs.prerelease || false }}
files: |
plugin_versions.json
release_plugins/**/*.py
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload Release Assets
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
find release_plugins -type f -name "*.py" -print0 | xargs -0 gh release upload ${{ steps.version.outputs.version }} --clobber
- name: Summary
run: |
echo "## 🚀 Release Created Successfully!" >> $GITHUB_STEP_SUMMARY

View File

@@ -6,6 +6,11 @@
Export chat conversations to Excel spreadsheet format for analysis, archiving, and sharing.
### What's New in v0.3.5
- **Export Scope**: Added `EXPORT_SCOPE` valve to choose between exporting tables from the "Last Message" (default) or "All Messages".
- **Smart Sheet Naming**: Automatically names sheets based on Markdown headers, AI titles (if enabled), or message index (e.g., `Msg1-Tab1`).
- **Multiple Tables Support**: Improved handling of multiple tables within single or multiple messages.
## What's New in v0.3.4
- **Smart Filename Generation**: Now supports generating filenames based on Chat Title, AI Summary, or Markdown Headers.

View File

@@ -6,7 +6,12 @@
将聊天记录导出为 Excel 表格,便于分析、归档和分享。
## v0.3.4 更新内容
### v0.3.5 更新内容
- **导出范围**: 新增 `EXPORT_SCOPE` 配置项,可选择导出“最后一条消息”(默认)或“所有消息”中的表格。
- **智能 Sheet 命名**: 根据 Markdown 标题、AI 标题(如启用)或消息索引(如 `消息1-表1`)自动命名 Sheet。
- **多表格支持**: 优化了对单条或多条消息中包含多个表格的处理。
### v0.3.4 更新内容
- **智能文件名生成**支持根据对话标题、AI 总结或 Markdown 标题生成文件名。
- **配置选项**:新增 `TITLE_SOURCE` 设置,用于控制文件名生成策略。

View File

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

View File

@@ -2,6 +2,11 @@
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.5
- **Export Scope**: Added `EXPORT_SCOPE` valve to choose between exporting tables from the "Last Message" (default) or "All Messages".
- **Smart Sheet Naming**: Automatically names sheets based on Markdown headers, AI titles (if enabled), or message index (e.g., `Msg1-Tab1`).
- **Multiple Tables Support**: Improved handling of multiple tables within single or multiple messages.
## What's New in v0.3.4
- **Smart Filename Generation**: Now supports generating filenames based on Chat Title, AI Summary, or Markdown Headers.

View File

@@ -2,6 +2,11 @@
此插件允许你直接从聊天界面将对话历史导出为 Excel (.xlsx) 文件。
### v0.3.5 更新内容
- **导出范围**: 新增 `EXPORT_SCOPE` 配置项,可选择导出“最后一条消息”(默认)或“所有消息”中的表格。
- **智能 Sheet 命名**: 根据 Markdown 标题、AI 标题(如启用)或消息索引(如 `消息1-表1`)自动命名 Sheet。
- **多表格支持**: 优化了对单条或多条消息中包含多个表格的处理。
## v0.3.4 更新内容
- **智能文件名生成**支持根据对话标题、AI 总结或 Markdown 标题生成文件名。

View File

@@ -3,7 +3,7 @@ title: Export to Excel
author: Fu-Jie
author_url: https://github.com/Fu-Jie
funding_url: https://github.com/Fu-Jie/awesome-openwebui
version: 0.3.4
version: 0.3.5
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
description: Exports the current chat history to an Excel (.xlsx) file, with automatic header extraction.
"""
@@ -30,6 +30,10 @@ class Action:
default="chat_title",
description="Title Source: 'chat_title' (Chat Title), 'ai_generated' (AI Generated), 'markdown_title' (Markdown Title)",
)
EXPORT_SCOPE: str = Field(
default="last_message",
description="Export Scope: 'last_message' (Last Message Only), 'all_messages' (All Messages)",
)
def __init__(self):
self.valves = self.Valves()
@@ -64,8 +68,6 @@ class Action:
user_id = __user__.get("id", "unknown_user")
if __event_emitter__:
last_assistant_message = body["messages"][-1]
await __event_emitter__(
{
"type": "status",
@@ -74,19 +76,115 @@ class Action:
)
try:
message_content = last_assistant_message["content"]
tables = self.extract_tables_from_message(message_content)
messages = body.get("messages", [])
if not messages:
raise HTTPException(status_code=400, detail="No messages found.")
if not tables:
raise HTTPException(status_code=400, detail="No tables found.")
# Determine messages to process based on scope
target_messages = []
if self.valves.EXPORT_SCOPE == "all_messages":
target_messages = messages
else:
target_messages = [messages[-1]]
# Generate filename
all_tables = []
all_sheet_names = []
# Process messages
for msg_index, msg in enumerate(target_messages):
content = msg.get("content", "")
tables = self.extract_tables_from_message(content)
if not tables:
continue
# Generate sheet names for this message's tables
# If multiple messages, we need to ensure uniqueness across the whole workbook
# We'll generate base names here and deduplicate later if needed,
# or better: generate unique names on the fly.
# Extract headers for this message
headers = []
lines = content.split("\n")
for i, line in enumerate(lines):
if re.match(r"^#{1,6}\s+", line):
headers.append(
{
"text": re.sub(r"^#{1,6}\s+", "", line).strip(),
"line_num": i,
}
)
for table_index, table in enumerate(tables):
sheet_name = ""
# 1. Try Markdown Header (closest above)
table_start_line = table["start_line"] - 1
closest_header_text = None
candidate_headers = [
h for h in headers if h["line_num"] < table_start_line
]
if candidate_headers:
closest_header = max(
candidate_headers, key=lambda x: x["line_num"]
)
closest_header_text = closest_header["text"]
if closest_header_text:
sheet_name = self.clean_sheet_name(closest_header_text)
# 2. AI Generated (Only if explicitly enabled and we have a request object)
# Note: Generating titles for EVERY table in all messages might be too slow/expensive.
# We'll skip this for 'all_messages' scope to avoid timeout, unless it's just one message.
if (
not sheet_name
and self.valves.TITLE_SOURCE == "ai_generated"
and len(target_messages) == 1
):
# Logic for AI generation (simplified for now, reusing existing flow if possible)
pass
# 3. Fallback: Message Index
if not sheet_name:
if len(target_messages) > 1:
# Use global message index (from original list if possible, but here we iterate target_messages)
# Let's use the loop index.
# If multiple tables in one message: "Msg 1 - Table 1"
if len(tables) > 1:
sheet_name = f"Msg{msg_index+1}-Tab{table_index+1}"
else:
sheet_name = f"Msg{msg_index+1}"
else:
# Single message (last_message scope)
if len(tables) > 1:
sheet_name = f"Table {table_index+1}"
else:
sheet_name = "Sheet1"
all_tables.append(table)
all_sheet_names.append(sheet_name)
if not all_tables:
raise HTTPException(
status_code=400, detail="No tables found in the selected scope."
)
# Deduplicate sheet names
final_sheet_names = []
seen_names = {}
for name in all_sheet_names:
base_name = name
counter = 1
while name in seen_names:
name = f"{base_name} ({counter})"
counter += 1
seen_names[name] = True
final_sheet_names.append(name)
# Generate Workbook Title (Filename)
# Use the title of the chat, or the first header of the first message with tables
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_id = self.extract_chat_id(body, None)
chat_title = ""
if chat_id:
chat_title = await self.fetch_chat_title(chat_id, user_id)
@@ -97,43 +195,29 @@ class Action:
):
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
# Try to find first header in the first message that has content
for msg in target_messages:
extracted = self.extract_title(msg.get("content", ""))
if extracted:
title = extracted
break
# Get dynamic filename and sheet names
workbook_name_from_content, sheet_names = (
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
# Fallback for filename
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:
if chat_title:
title = chat_title
else:
# Try extracting from content again if not already tried
if self.valves.TITLE_SOURCE != "markdown_title":
for msg in target_messages:
extracted = self.extract_title(msg.get("content", ""))
if extracted:
title = extracted
break
# Use optimized filename generation logic
current_datetime = datetime.datetime.now()
formatted_date = current_datetime.strftime("%Y%m%d")
# If no title found, use user_yyyymmdd format
if not title:
workbook_name = f"{user_name}_{formatted_date}"
else:
@@ -146,8 +230,10 @@ class Action:
os.makedirs(os.path.dirname(excel_file_path), exist_ok=True)
# Save tables to Excel (using enhanced formatting)
self.save_tables_to_excel_enhanced(tables, excel_file_path, sheet_names)
# Save tables to Excel
self.save_tables_to_excel_enhanced(
all_tables, excel_file_path, final_sheet_names
)
# Trigger file download
if __event_call__:
@@ -663,6 +749,28 @@ class Action:
}
)
# Bold cell style (for full cell bolding)
text_bold_format = workbook.add_format(
{
"border": 1,
"align": "left",
"valign": "vcenter",
"text_wrap": True,
"bold": True,
}
)
# Italic cell style (for full cell italics)
text_italic_format = workbook.add_format(
{
"border": 1,
"align": "left",
"valign": "vcenter",
"text_wrap": True,
"italic": True,
}
)
for i, table in enumerate(tables):
try:
table_data = table["data"]
@@ -734,6 +842,8 @@ class Action:
decimal_format,
date_format,
sequence_format,
text_bold_format,
text_italic_format,
)
except Exception as e:
@@ -757,6 +867,8 @@ class Action:
decimal_format,
date_format,
sequence_format,
text_bold_format=None,
text_italic_format=None,
):
"""
Apply enhanced formatting
@@ -765,6 +877,7 @@ class Action:
- Text: Left aligned
- Date: Center aligned
- Sequence: Center aligned
- Supports full cell Markdown bold (**text**) and italic (*text*)
"""
try:
# 1. Write headers (Center aligned)
@@ -826,7 +939,28 @@ class Action:
# Text - Left aligned
current_format = text_format
worksheet.write(row_idx + 1, col_idx, value, current_format)
if content_type == "text" and isinstance(value, str):
# Check for full cell bold (**text**)
match_bold = re.fullmatch(r"\*\*(.+)\*\*", value.strip())
# Check for full cell italic (*text*)
match_italic = re.fullmatch(r"\*(.+)\*", value.strip())
if match_bold:
# Extract content and apply bold format
clean_value = match_bold.group(1)
worksheet.write(
row_idx + 1, col_idx, clean_value, text_bold_format
)
elif match_italic:
# Extract content and apply italic format
clean_value = match_italic.group(1)
worksheet.write(
row_idx + 1, col_idx, clean_value, text_italic_format
)
else:
worksheet.write(row_idx + 1, col_idx, value, current_format)
else:
worksheet.write(row_idx + 1, col_idx, value, current_format)
# 4. Auto-adjust column width
for col_idx, column in enumerate(headers):
@@ -916,3 +1050,6 @@ class Action:
except Exception as e:
print(f"Error in basic formatting: {str(e)}")
except Exception as e:
print(f"Error in basic formatting: {str(e)}")

View File

@@ -3,7 +3,7 @@ title: 导出为 Excel
author: Fu-Jie
author_url: https://github.com/Fu-Jie
funding_url: https://github.com/Fu-Jie/awesome-openwebui
version: 0.3.4
version: 0.3.5
icon_url: data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik0xNSAySDZhMiAyIDAgMCAwLTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWN1oiLz48cGF0aCBkPSJNMTQgMnY0YTIgMiAwIDAgMCAyIDJoNCIvPjxwYXRoIGQ9Ik04IDEzaDIiLz48cGF0aCBkPSJNMTQgMTNoMiIvPjxwYXRoIGQ9Ik04IDE3aDIiLz48cGF0aCBkPSJNMTQgMTdoMiIvPjwvc3ZnPg==
description: 将当前对话历史导出为 Excel (.xlsx) 文件,支持自动提取表头。
"""
@@ -28,7 +28,11 @@ class Action:
class Valves(BaseModel):
TITLE_SOURCE: str = Field(
default="chat_title",
description="标题来源'chat_title' (对话标题), 'ai_generated' (AI生成), 'markdown_title' (Markdown标题)",
description="标题来源: 'chat_title' (对话标题), 'ai_generated' (AI生成), 'markdown_title' (Markdown标题)",
)
EXPORT_SCOPE: str = Field(
default="last_message",
description="导出范围: 'last_message' (仅最后一条消息), 'all_messages' (所有消息)",
)
def __init__(self):
@@ -50,43 +54,127 @@ class Action:
print(f"action:{__name__}")
if isinstance(__user__, (list, tuple)):
user_language = (
__user__[0].get("language", "zh-CN") if __user__ else "zh-CN"
__user__[0].get("language", "en-US") if __user__ else "en-US"
)
user_name = __user__[0].get("name", "用户") if __user__[0] else "用户"
user_name = __user__[0].get("name", "User") if __user__[0] else "User"
user_id = (
__user__[0]["id"]
if __user__ and "id" in __user__[0]
else "unknown_user"
)
elif isinstance(__user__, dict):
user_language = __user__.get("language", "zh-CN")
user_name = __user__.get("name", "用户")
user_language = __user__.get("language", "en-US")
user_name = __user__.get("name", "User")
user_id = __user__.get("id", "unknown_user")
if __event_emitter__:
last_assistant_message = body["messages"][-1]
await __event_emitter__(
{
"type": "status",
"data": {"description": "正在保存文件...", "done": False},
"data": {"description": "正在保存文件...", "done": False},
}
)
try:
message_content = last_assistant_message["content"]
tables = self.extract_tables_from_message(message_content)
messages = body.get("messages", [])
if not messages:
raise HTTPException(status_code=400, detail="未找到消息。")
if not tables:
raise HTTPException(status_code=400, detail="未找到任何表格。")
# Determine messages to process based on scope
target_messages = []
if self.valves.EXPORT_SCOPE == "all_messages":
target_messages = messages
else:
target_messages = [messages[-1]]
# 生成文件名
all_tables = []
all_sheet_names = []
# Process messages
for msg_index, msg in enumerate(target_messages):
content = msg.get("content", "")
tables = self.extract_tables_from_message(content)
if not tables:
continue
# Generate sheet names for this message's tables
# Extract headers for this message
headers = []
lines = content.split("\n")
for i, line in enumerate(lines):
if re.match(r"^#{1,6}\s+", line):
headers.append(
{
"text": re.sub(r"^#{1,6}\s+", "", line).strip(),
"line_num": i,
}
)
for table_index, table in enumerate(tables):
sheet_name = ""
# 1. Try Markdown Header (closest above)
table_start_line = table["start_line"] - 1
closest_header_text = None
candidate_headers = [
h for h in headers if h["line_num"] < table_start_line
]
if candidate_headers:
closest_header = max(
candidate_headers, key=lambda x: x["line_num"]
)
closest_header_text = closest_header["text"]
if closest_header_text:
sheet_name = self.clean_sheet_name(closest_header_text)
# 2. AI Generated (Only if explicitly enabled and we have a request object)
if (
not sheet_name
and self.valves.TITLE_SOURCE == "ai_generated"
and len(target_messages) == 1
):
pass
# 3. Fallback: Message Index
if not sheet_name:
if len(target_messages) > 1:
if len(tables) > 1:
sheet_name = f"消息{msg_index+1}-表{table_index+1}"
else:
sheet_name = f"消息{msg_index+1}"
else:
# Single message (last_message scope)
if len(tables) > 1:
sheet_name = f"{table_index+1}"
else:
sheet_name = "Sheet1"
all_tables.append(table)
all_sheet_names.append(sheet_name)
if not all_tables:
raise HTTPException(
status_code=400, detail="在选定范围内未找到表格。"
)
# Deduplicate sheet names
final_sheet_names = []
seen_names = {}
for name in all_sheet_names:
base_name = name
counter = 1
while name in seen_names:
name = f"{base_name} ({counter})"
counter += 1
seen_names[name] = True
final_sheet_names.append(name)
# Generate Workbook Title (Filename)
title = ""
chat_id = self.extract_chat_id(
body, None
) # metadata 在此版本 action 签名中不可用,但通常在 body 中
# 直接通过 chat_id 获取对话标题,因为 body 中通常缺少该信息
chat_id = self.extract_chat_id(body, None)
chat_title = ""
if chat_id:
chat_title = await self.fetch_chat_title(chat_id, user_id)
@@ -97,37 +185,27 @@ class Action:
):
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
for msg in target_messages:
extracted = self.extract_title(msg.get("content", ""))
if extracted:
title = extracted
break
# 获取动态文件名和sheet名称
workbook_name_from_content, sheet_names = (
self.generate_names_from_content(message_content, tables)
)
# 标题回退逻辑
# Fallback for filename
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:
if chat_title:
title = chat_title
else:
if self.valves.TITLE_SOURCE != "markdown_title":
for msg in target_messages:
extracted = self.extract_title(msg.get("content", ""))
if extracted:
title = extracted
break
# 使用优化后的文件名生成逻辑
current_datetime = datetime.datetime.now()
formatted_date = current_datetime.strftime("%Y%m%d")
# 如果没找到标题则使用 user_yyyymmdd 格式
if not title:
workbook_name = f"{user_name}_{formatted_date}"
else:
@@ -140,10 +218,12 @@ class Action:
os.makedirs(os.path.dirname(excel_file_path), exist_ok=True)
# 保存表格到Excel使用符合中国规范的格式化功能
self.save_tables_to_excel_enhanced(tables, excel_file_path, sheet_names)
# Save tables to Excel
self.save_tables_to_excel_enhanced(
all_tables, excel_file_path, final_sheet_names
)
# 触发文件下载
# Trigger file download
if __event_call__:
with open(excel_file_path, "rb") as file:
file_content = file.read()
@@ -174,7 +254,7 @@ class Action:
URL.revokeObjectURL(url);
document.body.removeChild(a);
}} catch (error) {{
console.error('触发下载时出错:', error);
console.error('Error triggering download:', error);
}}
"""
},
@@ -183,15 +263,15 @@ class Action:
await __event_emitter__(
{
"type": "status",
"data": {"description": "输出已保存", "done": True},
"data": {"description": "文件已保存", "done": True},
}
)
# 清理临时文件
# Clean up temp file
if os.path.exists(excel_file_path):
os.remove(excel_file_path)
return {"message": "下载事件已触发"}
return {"message": "下载已触发"}
except HTTPException as e:
print(f"Error processing tables: {str(e.detail)}")
@@ -199,13 +279,13 @@ class Action:
{
"type": "status",
"data": {
"description": f"保存文件时出错: {e.detail}",
"description": f"保存文件错: {e.detail}",
"done": True,
},
}
)
await self._send_notification(
__event_emitter__, "error", "没有找到可导出的表格!"
__event_emitter__, "error", "找到可导出的表格"
)
raise e
except Exception as e:
@@ -214,13 +294,13 @@ class Action:
{
"type": "status",
"data": {
"description": f"保存文件时出错: {str(e)}",
"description": f"保存文件错: {str(e)}",
"done": True,
},
}
)
await self._send_notification(
__event_emitter__, "error", "没有找到可导出的表格!"
__event_emitter__, "error", "找到可导出的表格"
)
async def generate_title_using_ai(
@@ -674,6 +754,28 @@ class Action:
}
)
# 粗体单元格样式 (用于全单元格加粗)
text_bold_format = workbook.add_format(
{
"border": 1,
"align": "left",
"valign": "vcenter",
"text_wrap": True,
"bold": True,
}
)
# 斜体单元格样式 (用于全单元格斜体)
text_italic_format = workbook.add_format(
{
"border": 1,
"align": "left",
"valign": "vcenter",
"text_wrap": True,
"italic": True,
}
)
for i, table in enumerate(tables):
try:
table_data = table["data"]
@@ -745,6 +847,8 @@ class Action:
decimal_format,
date_format,
sequence_format,
text_bold_format,
text_italic_format,
)
except Exception as e:
@@ -768,6 +872,8 @@ class Action:
decimal_format,
date_format,
sequence_format,
text_bold_format=None,
text_italic_format=None,
):
"""
应用符合中国官方表格规范的格式化
@@ -776,6 +882,7 @@ class Action:
- 文本: 左对齐
- 日期: 居中对齐
- 序号: 居中对齐
- 支持全单元格 Markdown 粗体 (**text**) 和斜体 (*text*)
"""
try:
# 1. 写入表头(居中对齐)
@@ -837,7 +944,28 @@ class Action:
# 文本类型 - 左对齐
current_format = text_format
worksheet.write(row_idx + 1, col_idx, value, current_format)
if content_type == "text" and isinstance(value, str):
# 检查是否全单元格加粗 (**text**)
match_bold = re.fullmatch(r"\*\*(.+)\*\*", value.strip())
# 检查是否全单元格斜体 (*text*)
match_italic = re.fullmatch(r"\*(.+)\*", value.strip())
if match_bold:
# 提取内容并应用粗体格式
clean_value = match_bold.group(1)
worksheet.write(
row_idx + 1, col_idx, clean_value, text_bold_format
)
elif match_italic:
# 提取内容并应用斜体格式
clean_value = match_italic.group(1)
worksheet.write(
row_idx + 1, col_idx, clean_value, text_italic_format
)
else:
worksheet.write(row_idx + 1, col_idx, value, current_format)
else:
worksheet.write(row_idx + 1, col_idx, value, current_format)
# 4. 自动调整列宽
for col_idx, column in enumerate(headers):
@@ -937,3 +1065,6 @@ class Action:
except Exception as e:
print(f"Warning: Even basic formatting failed: {str(e)}")
except Exception as e:
print(f"Warning: Even basic formatting failed: {str(e)}")

View File

@@ -295,6 +295,8 @@ def main():
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(output)
if not output.endswith("\n"):
f.write("\n")
print(f"Output written to {args.output}")
else:
print(output)