feat(attachments): 增加附件阅读解析能力
This commit is contained in:
@@ -6,10 +6,14 @@ from django.db.models import Q, QuerySet
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
from .file_summary.skills.attachment_reader import AttachmentReaderSkill
|
||||
from .file_summary.workflow import create_file_summary_batch, start_file_summary_workflow
|
||||
from .file_summary.workflow_trigger import evaluate_file_summary_trigger
|
||||
from .file_summary.workflow_trigger import (
|
||||
evaluate_attachment_reader_trigger,
|
||||
evaluate_file_summary_trigger,
|
||||
)
|
||||
from .llm import LLMConfigurationError, LLMRequestError, generate_reply, stream_reply
|
||||
from .models import Conversation, Message
|
||||
from .models import Conversation, FileAttachment, Message
|
||||
|
||||
|
||||
def list_conversations(user, search: str = "") -> QuerySet[Conversation]:
|
||||
@@ -92,6 +96,7 @@ def stream_message(conversation: Conversation, content: str):
|
||||
user_message = append_user_message(conversation, content)
|
||||
assistant_parts: list[str] = []
|
||||
trigger = evaluate_file_summary_trigger(conversation, content)
|
||||
attachment_reader_trigger = evaluate_attachment_reader_trigger(conversation, content)
|
||||
|
||||
yield sse_event(
|
||||
"meta",
|
||||
@@ -117,6 +122,36 @@ def stream_message(conversation: Conversation, content: str):
|
||||
)
|
||||
return
|
||||
|
||||
if attachment_reader_trigger.reason == "missing_attachment":
|
||||
reply_content = "请先在当前对话右侧上传需要阅读的附件,然后再发送解析或阅读附件指令。"
|
||||
assistant_message = append_assistant_message(conversation, reply_content)
|
||||
yield sse_event("chunk", {"delta": reply_content})
|
||||
yield sse_event(
|
||||
"done",
|
||||
{
|
||||
"assistant_message_id": assistant_message.pk,
|
||||
"conversation_id": conversation.pk,
|
||||
"title": conversation.title,
|
||||
},
|
||||
)
|
||||
return
|
||||
|
||||
if attachment_reader_trigger.should_start:
|
||||
attachments = _select_attachments_for_reader(conversation, content)
|
||||
result = AttachmentReaderSkill().run_for_attachments(attachments)
|
||||
reply_content = _format_attachment_reader_reply(result.data.get("attachments", []), result.message)
|
||||
assistant_message = append_assistant_message(conversation, reply_content)
|
||||
yield sse_event("chunk", {"delta": reply_content})
|
||||
yield sse_event(
|
||||
"done",
|
||||
{
|
||||
"assistant_message_id": assistant_message.pk,
|
||||
"conversation_id": conversation.pk,
|
||||
"title": conversation.title,
|
||||
},
|
||||
)
|
||||
return
|
||||
|
||||
if trigger.should_start:
|
||||
batch = create_file_summary_batch(
|
||||
conversation=conversation,
|
||||
@@ -182,6 +217,62 @@ def build_conversation_title(content: str) -> str:
|
||||
return normalized[:24]
|
||||
|
||||
|
||||
def _select_attachments_for_reader(conversation: Conversation, content: str):
|
||||
attachments = list(
|
||||
FileAttachment.objects.filter(
|
||||
conversation=conversation,
|
||||
is_active=True,
|
||||
)
|
||||
.exclude(upload_status=FileAttachment.UploadStatus.DELETED)
|
||||
.order_by("original_name", "-version_no")
|
||||
)
|
||||
matched = [attachment for attachment in attachments if attachment.original_name in content]
|
||||
return matched or attachments
|
||||
|
||||
|
||||
def _format_attachment_reader_reply(attachments: list[dict[str, object]], message: str) -> str:
|
||||
if not attachments:
|
||||
return message or "当前对话没有可读取的附件。"
|
||||
|
||||
lines = ["## 附件解析结果"]
|
||||
for item in attachments:
|
||||
status = item.get("status", "")
|
||||
filename = item.get("filename", "")
|
||||
file_type = item.get("file_type", "")
|
||||
lines.extend(
|
||||
[
|
||||
"",
|
||||
f"### {filename}",
|
||||
f"- 类型:{file_type or '未知'}",
|
||||
f"- 状态:{status}",
|
||||
]
|
||||
)
|
||||
if item.get("error_message"):
|
||||
lines.append(f"- 错误:{item['error_message']}")
|
||||
continue
|
||||
|
||||
preview = str(item.get("preview_text") or "").strip()
|
||||
if preview:
|
||||
lines.extend(["", "摘要预览:", "```text", preview, "```"])
|
||||
|
||||
sections = item.get("sections") or []
|
||||
if sections:
|
||||
lines.append("")
|
||||
lines.append("结构详情:")
|
||||
for section in sections[:8]:
|
||||
if not isinstance(section, dict):
|
||||
continue
|
||||
section_type = section.get("type", "section")
|
||||
name = section.get("name", "")
|
||||
extra = ""
|
||||
if "row_count" in section:
|
||||
extra = f",{section['row_count']} 行"
|
||||
if "column_count" in section:
|
||||
extra += f",{section['column_count']} 列"
|
||||
lines.append(f"- {name}({section_type}{extra})")
|
||||
return "\n".join(lines).strip()
|
||||
|
||||
|
||||
def sse_event(event_name: str, payload: dict[str, object]) -> str:
|
||||
"""Formats one server-sent event frame."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user