cockpit: file upload support — read files, inject to LLM, emit file widgets
- Parse file_paths from request, read file contents (UTF-8, max 8000 chars)
- File content appended to LLM message as 【附件文件内容】
- File widget streamed to frontend: 📎 filename (size) + hidden content preview
- Frontend script: click .file-name → toggle .file-content visibility
- File paths saved in pipeline_conversations.attachments
This commit is contained in:
parent
39deb68ba6
commit
2b0bc7519a
@ -310,12 +310,39 @@ _tool_names = {
|
||||
def _tool_label(tool):
|
||||
return _tool_names.get(tool, tool)
|
||||
|
||||
def _widget_file(filename, content_preview, file_size=0):
|
||||
"""Bricks widget: file attachment with expandable content preview."""
|
||||
size_str = f" ({file_size} bytes)" if file_size else ""
|
||||
return {
|
||||
"widgettype": "VBox",
|
||||
"options": {
|
||||
"css": "agent-file",
|
||||
"style": {"border": "1px solid #e2e8f0", "borderRadius": "8px", "padding": "8px 12px", "margin": "4px 0"}
|
||||
},
|
||||
"subwidgets": [
|
||||
{"widgettype": "HBox", "subwidgets": [
|
||||
{"widgettype": "Text", "options": {"text": f"📎 {filename}{size_str}", "css": "file-name",
|
||||
"style": {"color": "#2563eb", "cursor": "pointer", "fontSize": "13px", "fontWeight": "bold"}}}
|
||||
]},
|
||||
{"widgettype": "Text", "options": {"text": content_preview or "(空文件)", "css": "file-content",
|
||||
"style": {"display": "none", "whiteSpace": "pre-wrap", "fontSize": "12px", "color": "#475569",
|
||||
"background": "#f8fafc", "borderRadius": "4px", "padding": "8px", "marginTop": "6px",
|
||||
"maxHeight": "300px", "overflowY": "auto"}}}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
# ── Streaming Agent Loop ──
|
||||
|
||||
if action == 'send_message':
|
||||
message_text = (params_kw or {}).get('message_text', '').strip()
|
||||
user_model_id = (params_kw or {}).get('model_id', '')
|
||||
file_paths_raw = (params_kw or {}).get('file_paths', '[]')
|
||||
file_paths = []
|
||||
try:
|
||||
file_paths = json.loads(file_paths_raw) if isinstance(file_paths_raw, str) else (file_paths_raw or [])
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
file_paths = []
|
||||
|
||||
if not message_text:
|
||||
return json.dumps({"error": "message_text is required"}, ensure_ascii=False)
|
||||
@ -346,8 +373,29 @@ if action == 'send_message':
|
||||
for h in (history or []):
|
||||
msgs.append({"role":'user' if getattr(h,'role','')=='user' else 'assistant',"content":getattr(h,'content','')})
|
||||
|
||||
await sor.C('pipeline_conversations',{'id':getID(),'iteration_id':'','task_id':'','role':'user','content':message_text,'attachments':'[]','msg_type':'text','org_id':org_id,'created_by':uid})
|
||||
msgs.append({"role":"user","content":message_text})
|
||||
await sor.C('pipeline_conversations',{'id':getID(),'iteration_id':'','task_id':'','role':'user','content':message_text,'attachments':json.dumps(file_paths),'msg_type':'text','org_id':org_id,'created_by':uid})
|
||||
|
||||
# 处理上传文件:读取内容、发送文件widget、注入LLM上下文
|
||||
file_contents = []
|
||||
for fp in file_paths:
|
||||
try:
|
||||
if os.path.isfile(fp):
|
||||
with open(fp, 'r', encoding='utf-8', errors='replace') as f:
|
||||
content = f.read()[:8000]
|
||||
fsize = os.path.getsize(fp)
|
||||
fname = os.path.basename(fp)
|
||||
file_contents.append(f"=== 文件: {fname} ===\n{content}")
|
||||
# 发送文件widget
|
||||
preview = content[:1000] + ("\n...(截断)" if len(content) > 1000 else "")
|
||||
yield json.dumps({"type":"widget","widget":_widget_file(fname, preview, fsize)}, ensure_ascii=False) + '\n'
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 把用户消息+文件内容合并为一条LLM消息
|
||||
full_message = message_text
|
||||
if file_contents:
|
||||
full_message = message_text + "\n\n【附件文件内容】\n" + "\n\n".join(file_contents)
|
||||
msgs.append({"role":"user","content":full_message})
|
||||
|
||||
agent_reply = ''
|
||||
for turn in range(10):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user