Agent2Agent整合多个Mcp
该摘要介绍了基于Google GitHub Agent2Agent的Python项目依赖项列表,以及Mcp工具集的相关功能。项目依赖包括Google AI服务、LangChain、FastAPI等204个Python包。Mcp部分展示了两个功能模块:1) Office Word操作工具,包含获取当前目录、覆盖写入文件等功能;2) UML图绘制工具,通过PlantUML实现,提供了保存文本到文件的基
·
版本
依赖于google的 github Agent2Agent/samples/python
其他依赖:
absl-py==2.2.2
annotated-types==0.7.0
anyio==4.9.0
appdirs==1.4.4
asttokens==3.0.0
asyncclick==8.1.8.0
Authlib==1.5.2
blinker==1.9.0
cachetools==5.5.2
certifi==2025.4.26
cffi==1.17.1
charset-normalizer==3.4.2
click==8.1.8
colorama==0.4.6
cryptography==44.0.3
decorator==5.2.1
deepdiff==6.7.1
Deprecated==1.2.18
distro==1.9.0
docstring_parser==0.16
docx2pdf==0.1.8
dotenv==0.9.9
exceptiongroup==1.3.0
executing==2.2.0
fastapi==0.115.12
fastmcp==2.3.3
filetype==1.2.0
Flask==3.1.0
google-adk==0.5.0
google-ai-generativelanguage==0.6.18
google-api-core==2.24.2
google-api-python-client==2.169.0
google-auth==2.40.1
google-auth-httplib2==0.2.0
google-cloud-aiplatform==1.92.0
google-cloud-bigquery==3.31.0
google-cloud-core==2.4.3
google-cloud-resource-manager==1.14.2
google-cloud-secret-manager==2.23.3
google-cloud-speech==2.32.0
google-cloud-storage==2.19.0
google-cloud-trace==1.16.1
google-crc32c==1.7.1
google-genai==1.14.0
google-resumable-media==2.7.2
googleapis-common-protos==1.70.0
graphviz==0.20.3
greenlet==3.2.1
grpc-google-iam-v1==0.14.2
grpcio==1.72.0
grpcio-status==1.72.0
h11==0.16.0
httpcore==1.0.9
httplib2==0.22.0
httpx==0.28.1
httpx-sse==0.4.0
idna==3.10
importlib_metadata==8.6.1
ipython==9.2.0
ipython_pygments_lexers==1.1.1
itsdangerous==2.2.0
jedi==0.19.2
Jinja2==3.1.6
jiter==0.9.0
jsonpatch==1.33
jsonpointer==3.0.0
jwcrypto==1.5.6
langchain-core==0.3.58
langchain-google-genai==2.1.4
langchain-mcp-adapters==0.0.10
langchain-openai==0.3.16
langgraph==0.4.2
langgraph-checkpoint==2.0.25
langgraph-prebuilt==0.1.8
langgraph-sdk==0.1.66
langsmith==0.3.42
lxml==6.0.0
markdown-it-py==3.0.0
MarkupSafe==3.0.2
matplotlib-inline==0.1.7
mcp==1.8.1
mdurl==0.1.2
mesop==1.0.1
msgpack==1.1.0
msoffcrypto-tool==5.4.2
numpy==2.2.5
olefile==0.47
openai==1.77.0
openapi-pydantic==0.5.1
opentelemetry-api==1.32.1
opentelemetry-exporter-gcp-trace==1.9.0
opentelemetry-resourcedetector-gcp==1.9.0a0
opentelemetry-sdk==1.32.1
opentelemetry-semantic-conventions==0.53b1
ordered-set==4.1.0
orjson==3.10.18
ormsgpack==1.9.1
packaging==24.2
pandas==2.2.3
parso==0.8.4
prompt_toolkit==3.0.51
proto-plus==1.26.1
protobuf==6.31.0rc2
pure_eval==0.2.3
pyasn1==0.6.1
pyasn1_modules==0.4.2
pycparser==2.22
pydantic==2.11.4
pydantic-settings==2.9.1
pydantic_core==2.33.2
pyee==11.1.1
Pygments==2.19.1
PyJWT==2.10.1
pyparsing==3.2.3
pyppeteer==2.0.0
python-dateutil==2.9.0.post0
python-docx==1.2.0
python-dotenv==1.1.0
python-multipart==0.0.20
pytz==2025.2
pywin32==311
PyYAML==6.0.2
regex==2024.11.6
requests==2.32.3
requests-toolbelt==1.0.0
rich==14.0.0
rsa==4.9.1
shapely==2.1.0
shellingham==1.5.4
six==1.17.0
sniffio==1.3.1
SQLAlchemy==2.0.40
sse-starlette==2.3.4
stack-data==0.6.3
starlette==0.46.2
tenacity==9.1.2
tiktoken==0.9.0
tqdm==4.67.1
traitlets==5.14.3
typer==0.15.3
typing-inspection==0.4.0
typing_extensions==4.13.2
tzdata==2025.2
tzlocal==5.3.1
uritemplate==4.1.1
urllib3==1.26.20
uv==0.7.2
uvicorn==0.34.2
watchdog==6.0.0
wcwidth==0.2.13
websockets==14.0
Werkzeug==3.1.3
wrapt==1.17.2
xxhash==3.5.0
zipp==3.21.0
zstandard==0.23.0
Mcp
word操作mcp
GongRzhe/Office-Word-MCP-Server/
UML图绘制mcp
1 . plantuml官网下载jar包
2. 编写python mcp tool
import os
import subprocess
from fastmcp import FastMCP
from typing import Union
mcp = FastMCP("plant uml")
@mcp.tool()
def get_current_directory():
"""
获取当前工作目录。
返回:
str: 当前工作目录的路径
"""
current_dir = os.getcwd()
print(f"当前工作目录是: {current_dir}")
return current_dir
@mcp.tool()
def overwrite_file(content: Union[str, bytes], file_path: str) -> bool:
"""
全量覆盖更新文件内容,仅在内容变化时执行写入操作
参数:
content (str|bytes): 要写入的内容,可以是字符串或字节
file_path (str): 目标文件路径
返回:
bool: 是否执行了写入操作(True=已写入,False=未写入或失败)
示例:
>>> overwrite_file("new content", "/path/to/file.txt")
True
"""
try:
# 标准化路径并创建目录
file_path = os.path.normpath(file_path)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
# 检查内容是否变化
if os.path.exists(file_path):
mode = 'rb' if isinstance(content, bytes) else 'r'
with open(file_path, mode) as f:
existing_content = f.read()
if existing_content == content:
print(f"内容未变化,跳过写入: {file_path}")
return False
# 执行写入操作
mode = 'wb' if isinstance(content, bytes) else 'w'
encoding = None if isinstance(content, bytes) else 'utf-8'
with open(file_path, mode, encoding=encoding) as f:
f.write(content)
print(f"成功更新文件: {file_path}")
return True
except Exception as e:
print(f"文件更新失败: {file_path} - {str(e)}")
return False
@mcp.tool()
def save_text_to_file(content, file_path):
"""
将指定文本保存到指定路径的文件中
参数:
content (str): 要保存的文本内容
file_path (str): 文件的完整路径,例如 'D:/example.puml'
返回:
bool: 如果成功则返回 True,否则返回 False
"""
try:
# 创建目录(如果不存在)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w', encoding='utf-8') as file:
file.write(content)
print(f"成功将文本保存到 {file_path}")
return True
except Exception as e:
print(f"保存失败: {e}")
return False
@mcp.tool()
def plant_uml(file_name):
"""
渲染输入的*.plum文件为 png格式的UML 图
参数:
file_name (str): plantuml 文件的完整路径,例如 'D:/test.puml'
返回:
bool: 如果成功则返回 True,否则返回 False
"""
# 确保文件存在
if not os.path.exists(file_name):
print(f"文件 {file_name} 不存在。")
return False
# 构造命令
command = ["java", "-jar", "plantuml-epl-SNAPSHOT.jar", file_name]
try:
# 执行命令
subprocess.run(command, check=True)
print(f"成功生成 {file_name} 的图片。")
return True
except subprocess.CalledProcessError as e:
print(f"渲染失败: {e}")
return False
if __name__ == '__main__':
mcp.run(
transport='sse',
host="127.0.0.1",
port=9001,
path="/sse"
)
Agent2Agent
doc_agent.py
组装llm和mcp的类
import asyncio
import time
from google.adk.tools.mcp_tool.mcp_session_manager import SseServerParams
from langchain_core.messages import HumanMessage
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_mcp_adapters.tools import load_mcp_tools
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from mcp import StdioServerParameters, ClientSession, stdio_client
from mcp.client.sse import sse_client
from langgraph.checkpoint.memory import MemorySaver # 内存型检查点存储
class DocAgent:
def __init__(self):
self.model = ChatOpenAI(base_url="http://your_url/openai/v1", model="deepseek-chat",
streaming=True,
api_key="your_key")
self.client = MultiServerMCPClient(
{
"PlantUmlServer": {
"url": "http://127.0.0.1:9001/sse",
"transport": "sse"
},
"OperateDocFileServer": {
"url": "http://127.0.0.1:8888/sse",
"transport": "sse"
}
}
)
async def open(self):
await self.client.__aenter__()
self.tools = self.client.get_tools()
print(self.tools)
self.agent = create_react_agent(model=self.model, tools=self.tools, prompt="""你是一个doc项目文档编写助手""",
checkpointer=MemorySaver())
async def close(self):
await self.client.__aexit__(None, None, None)
async def agent_chat(self,query: str):
state = await self.agent.aget_state({"configurable":{"thread_id": "1"}})
# 从状态中获取 messages
messages = state.values.get("messages", [])
begin_index = len(messages)
agent_response = await self.agent.ainvoke({
"messages":query
},
{"configurable":{"thread_id": "1"}}
)
messages = agent_response.get("messages")
if begin_index != 0:
messages = messages[begin_index:]
print("\n")
for message in messages:
print(message)
content = messages[-1].content
print(f"\n agent final response: {content}")
return messages
async def begin():
doc_agent = DocAgent()
await doc_agent.open()
result= await doc_agent.agent_chat("""据如下sql最终完成绘制uml的er实体关系图:""")
await doc_agent.close()
if __name__ == "__main__":
asyncio.run(begin())
dock_task_manager.py
a2a的task manager
from agents.wind_doc.doc_agent import DocAgent
import asyncio
from typing import AsyncIterable
from urllib import request
from common.server import InMemoryTaskManager
from common.types import SendTaskRequest, SendTaskResponse, SendTaskStreamingRequest, SendTaskStreamingResponse, \
JSONRPCResponse, TaskState, TaskStatus, Message, Artifact, Task, TaskStatusUpdateEvent
from common.types import FileContent,FilePart
from pathlib import Path
import base64
from langchain_core.messages import BaseMessage
from file_util import convert_to_download_url
from pyasn1_modules.rfc7906 import id_enumeratedRestrictiveAttributes
class DocAgentTaskManager(InMemoryTaskManager):
def __init__(self):
super().__init__()
self.doc_agent = None
async def init(self):
self.doc_agent = DocAgent()
await self.doc_agent.open()
async def destory(self):
await self.doc_agent.close()
async def on_send_task(self, request: SendTaskRequest) -> SendTaskResponse:
await self.upsert_task(request.params)
task_id = request.params.id
received_text = request.params.message.parts[0].text
if self.doc_agent is None:
await self.init()
messages = await self.doc_agent.agent_chat(received_text)
chat_result = messages[-1].content
await self._update_task_messages_text(
task_id=task_id,
task_state= TaskState.COMPLETED,
messages=messages
)
task = await self._update_task_message_text(
task_id=task_id,
task_state=TaskState.COMPLETED,
response_text=f"{chat_result}"
)
await self.process_tool_outcome(task_id, TaskState.COMPLETED, messages)
task = self.tasks[task_id]
return SendTaskResponse(id=request.id,result=task)
async def on_send_task_subscribe(self, request: SendTaskStreamingRequest) -> AsyncIterable[
SendTaskStreamingResponse] | JSONRPCResponse:
await self.upsert_task(request.params)
task_id = request.params.id
is_new_task = task_id in self.tasks
await self.upsert_task(request.params)
received_text = request.params.message.parts[0].text
sse_event_queue = await self.setup_sse_consumer(task_id=task_id)
return self.dequeue_events_for_sse(request_id=request.id, task_id=task_id, sse_event_queue=sse_event_queue)
async def process_tool_outcome(self, task_id: str, task_state: TaskState, messages: list[BaseMessage]):
args_map = {}
for message in messages:
if message.type == "ai":
tool_calls = message.tool_calls
for tool_call in tool_calls:
args_map[tool_call["id"]] = tool_call["args"]
if message.type == "tool":
tool_call_id = message.tool_call_id
name = message.name
if name == "create_document":
args = args_map[tool_call_id]
filename = args["filename"]
url = convert_to_download_url(filename)
await self._update_task_artifact_file_uri(task_id,task_state,url)
if name == "plant_uml":
args = args_map[tool_call_id]
file_name = args["file_name"]
await self._update_task_artifact_img(task_id,task_state,file_name)
async def _update_task_artifact_img(self,task_id: str, task_state: TaskState,image_path: str):
path = Path(image_path.replace(".puml",".png"))
# Read file as binary and encode as base64 string
with open(path, 'rb') as f:
file_bytes = f.read()
base64_bytes = base64.b64encode(file_bytes).decode('utf-8')
# 构建 FileContent 使用 URI 方式
file_content = FileContent(
name="er_diagram.png",
mimeType="image/png",
bytes=base64_bytes
)
# 构建 FilePart
file_part = FilePart(
file=file_content
)
task = self.tasks[task_id]
if task.artifacts is None:
task.artifacts=[]
task.artifacts.append(Artifact(parts=[file_part]))
self.tasks[task_id] = task
return task
async def _update_task_artifact_file_uri(self, task_id: str, task_state: TaskState, uri: str):
# 构建 FileContent 使用 URI 方式
file_content = FileContent(
name=uri.split("-1")[-1],
mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
uri=uri
)
# 构建 FilePart
file_part = FilePart(
file=file_content
)
task = self.tasks[task_id]
if task.artifacts is None:
task.artifacts=[]
task.artifacts.append(Artifact(parts=[file_part]))
self.tasks[task_id] = task
return task
async def _update_task_message_text(self, task_id: str, task_state: TaskState, response_text: str) -> Task:
agent_response_parts = [
{
"type": "text",
"text": response_text,
}
]
task = self.tasks[task_id]
task.status = TaskStatus(state=task_state, message=Message(role="agent", parts=agent_response_parts))
if task.artifacts is None:
task.artifacts=[]
task.artifacts.append( Artifact(parts=agent_response_parts))
self.tasks[task_id] = task
return task
async def _update_task_messages_text(self, task_id: str, task_state: TaskState, messages: list[str]) -> Task:
agent_response_parts = []
for message in messages:
if message.type == "tool":
agent_response_parts.append({
"type": "text",
"text": "【工具调用】 ["+message.name+"] 执行结果: "+message.content,
})
task = self.tasks[task_id]
task.status = TaskStatus(state=task_state, message=Message(role="agent", parts=agent_response_parts))
if task.artifacts is None:
task.artifacts = []
task.artifacts.append(Artifact(parts=agent_response_parts))
self.tasks[task_id] = task
return task
main.py
主函数
import asyncio
import click
from agents.wind_doc.doc_task_manager import DocAgentTaskManager
from common.server import A2AServer
from common.types import AgentSkill, AgentCapabilities, AgentCard
from exceptiongroup import catch
def main():
try:
host = "127.0.0.1"
port= 10001
skill = AgentSkill(
id="doc-operate-skill",
name="operator doc file",
description="operator doc file",
tags=["doc"],
examples=["create test.doc on D:/Document"],
inputModes=["text"],
outputModes=["text"],
)
print(skill)
capabilities = AgentCapabilities()
agent_card = AgentCard(name="doc-operate-agent", description="program doc operate agent", url=f"http://{host}:{port}",
version="0.1.0", defaultInputModes=["text"], defaultOutputModes=["text"], capabilities=capabilities,
skills=[skill])
print(agent_card)
task_manager = DocAgentTaskManager()
task_manager.init()
server = A2AServer(agent_card= agent_card,task_manager=task_manager,host=host,port=port)
server.start()
print("Server started")
except KeyboardInterrupt:
print("shut down")
finally:
task_manager.destory()
if __name__ == '__main__':
main()
file_system.py
启一个文件服务器,方便a2a client下载文件.
import os
from http.server import HTTPServer, SimpleHTTPRequestHandler
def run_http_server(directory="D:\\Document", port=8000):
"""
启动 HTTP 服务器(前台运行,按 Ctrl+C 停止)
"""
os.chdir(directory)
server = HTTPServer(('0.0.0.0', port), SimpleHTTPRequestHandler)
print(f"服务器已启动,访问: http://localhost:{port}")
print("按 Ctrl+C 停止服务器...")
try:
server.serve_forever() # 阻塞,直到手动停止
except KeyboardInterrupt:
print("\n服务器已停止")
# 启动服务器(会阻塞,直到手动停止)
run_http_server()
file_util.py
把本地路径转为远端可访问的uri
import os
from urllib.parse import quote
def convert_to_download_url(file_path, server_base_url="http://localhost:8000"):
"""
将本地文件路径转换为可下载的URL,仅当文件位于 D:\Document 目录下时才允许转换
参数:
file_path (str): 本地文件的绝对路径
server_base_url (str): 文件服务器的基础URL(默认 http://localhost:8000)
返回:
str: 可下载的URL,如果文件不在 D:\Document 下则返回 None
示例:
>>> convert_to_download_url("D:\\Document\\test.txt")
"http://localhost:8000/test.txt"
>>> convert_to_download_url("C:\\Other\\file.txt")
None # 文件不在 D:\Document 下,不允许访问
"""
# 标准化路径,避免不同格式(如 D:/Document vs D:\Document)
normalized_path = os.path.normpath(file_path)
document_dir = os.path.normpath("D:\\Document")
# 检查文件是否在 D:\Document 或其子目录下
if not normalized_path.startswith(document_dir + os.sep):
print(f"错误:文件 '{file_path}' 不在 D:\\Document 目录下,无法生成URL")
return None
# 检查文件是否存在
if not os.path.exists(file_path):
print(f"错误:文件 '{file_path}' 不存在")
return None
# 获取相对路径(相对于 D:\Document)
relative_path = os.path.relpath(normalized_path, document_dir)
# URL编码文件名(处理空格、中文等特殊字符)
encoded_filename = quote(relative_path.replace(os.sep, '/'))
# 构建完整的下载URL
download_url = f"{server_base_url.rstrip('/')}/{encoded_filename}"
return download_url
a2aClient.html
一个静态页面,用于访问A2A协议的server
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Connector</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.container {
display: flex;
flex-direction: column;
gap: 20px;
}
.input-group {
display: flex;
gap: 10px;
}
input, textarea {
flex: 1;
padding: 8px;
font-size: 16px;
}
button {
padding: 8px 16px;
background-color: #4CAF50;
color: white;
border: none;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
#agentInfo, #conversation {
border: 1px solid #ddd;
padding: 15px;
border-radius: 5px;
background-color: #f9f9f9;
white-space: pre-wrap;
font-family: monospace;
}
.status {
padding: 10px;
border-radius: 5px;
margin-bottom: 10px;
}
.success {
background-color: #dff0d8;
color: #3c763d;
}
.error {
background-color: #f2dede;
color: #a94442;
}
.chat-message {
margin: 10px 0;
padding: 8px;
border-radius: 4px;
}
.user-message {
background-color: #e3f2fd;
text-align: right;
}
.agent-message {
background-color: #f1f1f1;
}
#dialogContainer {
display: none;
margin-top: 20px;
}
/* 图片消息样式 */
.chat-message img {
max-width: 100%;
border-radius: 4px;
margin-top: 8px;
border: 1px solid #ddd;
}
/* 二进制数据提示 */
.binary-meta {
font-size: 12px;
color: #666;
margin-top: 4px;
}
</style>
</head>
<body>
<div class="container">
<h1>Agent Connector</h1>
<div class="input-group">
<input type="text" id="agentUrl" placeholder="Enter Agent URL" value="http://localhost:10001">
<button id="connectBtn">Connect</button>
</div>
<div id="statusContainer"></div>
<h2>Agent Information</h2>
<div id="agentInfo">Not connected</div>
<!-- 新增的对话界面 -->
<div id="dialogContainer">
<h2>Conversation</h2>
<div id="conversation"></div>
<div class="input-group">
<textarea id="messageInput" placeholder="Type your message here..." rows="3"></textarea>
</div>
<button id="sendBtn">Send Message</button>
</div>
</div>
<script>
// 全局变量存储连接信息
let currentSessionId = Date.now().toString(); // 生成唯一会话ID
let agentBaseUrl = '';
document.getElementById('connectBtn').addEventListener('click', async function() {
const agentUrl = document.getElementById('agentUrl').value.trim();
const statusContainer = document.getElementById('statusContainer');
const agentInfo = document.getElementById('agentInfo');
if (!agentUrl) {
showStatus('Please enter a valid Agent URL', 'error');
return;
}
try {
showStatus('Connecting to agent...', 'success');
// 测试连接
const response = await fetch(`${agentUrl}/.well-known/agent.json`);
if (!response.ok) {
throw new Error(`Agent connection failed: ${response.status}`);
}
const data = await response.json();
agentInfo.textContent = JSON.stringify(data, null, 2);
showStatus('Successfully connected to agent!', 'success');
// 连接成功后显示对话界面
agentBaseUrl = agentUrl;
document.getElementById('dialogContainer').style.display = 'block';
} catch (error) {
showStatus(`Error: ${error.message}`, 'error');
agentInfo.textContent = 'Failed to load agent information';
console.error('Connection error:', error);
}
});
// 发送消息到A2A服务器
document.getElementById('sendBtn').addEventListener('click', async function() {
const messageInput = document.getElementById('messageInput').value.trim();
const conversation = document.getElementById('conversation');
if (!messageInput) {
showStatus('Please enter a message', 'error');
return;
}
try {
// 添加用户消息到对话界面
addMessageToConversation('user', messageInput);
// 清空输入框
document.getElementById('messageInput').value = '';
// 准备请求数据
const requestData = {
"jsonrpc": "2.0",
"method": "tasks/send",
"params": {
"id": Date.now().toString(),
"sessionId": currentSessionId,
"message": {
"role": "user",
"parts": [{
"type": "text",
"text": messageInput
}]
}
}
};
showStatus('Sending message to agent...', 'success');
// 发送请求到A2A服务器
const response = await fetch(`${agentBaseUrl}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData)
});
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
const responseData = await response.json();
// 添加AI响应到对话界面
if (responseData.result) {
addMessageToConversation('agent', JSON.stringify(responseData.result, null, 2));
} else {
addMessageToConversation('agent', JSON.stringify(responseData, null, 2));
}
console.log('Full agent response:', responseData);
// 处理二进制附件(如图片)
if (responseData.result.artifacts) {
responseData.result.artifacts.forEach(artifact => {
console.log("one artifact")
artifact.parts.forEach(part => {
console.log("one part")
if (part.type === 'file' && part.file?.mimeType?.startsWith('image/')) {
console.log("one part is img")
displayBinaryImage(part);
}
});
});
}
showStatus('Message sent successfully!', 'success');
} catch (error) {
showStatus(`Error: ${error.message}`, 'error');
console.error('Message sending error:', error);
}
});
// 辅助函数:显示状态消息
function showStatus(message, type) {
const statusContainer = document.getElementById('statusContainer');
const statusDiv = document.createElement('div');
statusDiv.className = `status ${type}`;
statusDiv.textContent = message;
statusContainer.innerHTML = '';
statusContainer.appendChild(statusDiv);
}
// 辅助函数:添加消息到对话界面
function addMessageToConversation(role, content) {
const conversation = document.getElementById('conversation');
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${role}-message`;
const roleSpan = document.createElement('span');
roleSpan.textContent = `${role}: `;
roleSpan.style.fontWeight = 'bold';
const contentSpan = document.createElement('span');
contentSpan.textContent = content;
messageDiv.appendChild(roleSpan);
messageDiv.appendChild(document.createElement('br'));
messageDiv.appendChild(contentSpan);
conversation.appendChild(messageDiv);
conversation.scrollTop = conversation.scrollHeight;
}
// 显示二进制图片
function displayBinaryImage(part) {
const conversation = document.getElementById('conversation');
const messageDiv = document.createElement('div');
messageDiv.className = 'chat-message agent-message';
// 从Base64数据创建图片
const img = document.createElement('img');
img.src = `data:${part.file.mimeType};base64,${part.file.bytes}`;
console.log(img)
// 添加文件名提示
const metaDiv = document.createElement('div');
metaDiv.className = 'binary-meta';
metaDiv.textContent = `图片附件: ${part.file.name}`;
messageDiv.appendChild(img);
messageDiv.appendChild(metaDiv);
conversation.appendChild(messageDiv);
conversation.scrollTop = conversation.scrollHeight;
}
// 将ArrayBuffer转换为Base64
function arrayBufferToBase64(buffer) {
let binary = '';
const bytes = new Uint8Array(buffer);
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.bzip2 ? window.bzip2(binary) : binary;
}
</script>
</body>
</html>
更多推荐
所有评论(0)