fix(web): prevent duplicate image previews

This commit is contained in:
zhayujie
2026-04-18 22:32:34 +08:00
parent 14a119c48c
commit d25b8966ce
4 changed files with 27 additions and 14 deletions

View File

@@ -659,8 +659,11 @@ class AgentStreamExecutor:
tool_calls_buffer[index]["arguments"] += func["arguments"] tool_calls_buffer[index]["arguments"] += func["arguments"]
# Preserve _gemini_raw_parts for Gemini thoughtSignature round-trip # Preserve _gemini_raw_parts for Gemini thoughtSignature round-trip
# (direct Gemini: list of parts; LinkAI proxy: base64 string of JSON parts)
if "_gemini_raw_parts" in delta: if "_gemini_raw_parts" in delta:
gemini_raw_parts = delta["_gemini_raw_parts"] gemini_raw_parts = delta["_gemini_raw_parts"]
elif isinstance(choice, dict) and choice.get("_gemini_raw_parts"):
gemini_raw_parts = choice["_gemini_raw_parts"]
except Exception as e: except Exception as e:
error_str = str(e) error_str = str(e)

View File

@@ -297,8 +297,12 @@ class ChatChannel(Channel):
logger.debug("[chat_channel] sending reply: {}, context: {}".format(reply, context)) logger.debug("[chat_channel] sending reply: {}, context: {}".format(reply, context))
# 如果是文本回复,尝试提取并发送图片 # 如果是文本回复,尝试提取并发送图片
if reply.type == ReplyType.TEXT: # Web channel renders images/videos inline via renderMarkdown,
# so skip the extract-and-send step to avoid duplicate media.
if reply.type == ReplyType.TEXT and context.get("channel_type") != "web":
self._extract_and_send_images(reply, context) self._extract_and_send_images(reply, context)
elif reply.type == ReplyType.TEXT:
self._send(reply, context)
# 如果是图片回复但带有文本内容,先发文本再发图片 # 如果是图片回复但带有文本内容,先发文本再发图片
elif reply.type == ReplyType.IMAGE_URL and hasattr(reply, 'text_content') and reply.text_content: elif reply.type == ReplyType.IMAGE_URL and hasattr(reply, 'text_content') and reply.text_content:
# 先发送文本 # 先发送文本

View File

@@ -436,7 +436,6 @@ class GoogleGeminiBot(Bot):
tool_result_data = {"result": tool_content} tool_result_data = {"result": tool_content}
# Find the tool name from previous messages # Find the tool name from previous messages
# Look for the corresponding tool_call in model's message
tool_name = None tool_name = None
for prev_msg in reversed(messages): for prev_msg in reversed(messages):
if prev_msg.get("role") == "assistant": if prev_msg.get("role") == "assistant":
@@ -450,13 +449,14 @@ class GoogleGeminiBot(Bot):
if tool_name: if tool_name:
break break
# Gemini functionResponse format # Gemini functionResponse format (Gemini 3 requires `id`)
parts.append({ fn_response = {
"functionResponse": { "name": tool_name or "unknown",
"name": tool_name or "unknown", "response": tool_result_data
"response": tool_result_data }
} if tool_use_id:
}) fn_response["id"] = tool_use_id
parts.append({"functionResponse": fn_response})
elif "text" in block: elif "text" in block:
# Generic text field # Generic text field
@@ -624,10 +624,11 @@ class GoogleGeminiBot(Bot):
# Check for functionCall (per REST API docs) # Check for functionCall (per REST API docs)
if "functionCall" in part: if "functionCall" in part:
fc = part["functionCall"] fc = part["functionCall"]
logger.info(f"[Gemini] Function call detected: {fc.get('name')}") fc_id = fc.get("id") or f"call_{int(time.time() * 1000000)}"
logger.info(f"[Gemini] Function call detected: {fc.get('name')} (id={fc_id})")
tool_calls.append({ tool_calls.append({
"id": f"call_{int(time.time() * 1000000)}", "id": fc_id,
"type": "function", "type": "function",
"function": { "function": {
"name": fc.get("name"), "name": fc.get("name"),
@@ -747,10 +748,12 @@ class GoogleGeminiBot(Bot):
# Collect function calls # Collect function calls
if "functionCall" in part: if "functionCall" in part:
fc = part["functionCall"] fc = part["functionCall"]
logger.info(f"[Gemini] Function call: {fc.get('name')}") logger.info(f"[Gemini] Function call: {fc.get('name')} (id={fc.get('id')})")
# Prefer Gemini's native id; fall back to generated one
fc_id = fc.get("id") or f"call_{int(time.time() * 1000000)}_{len(all_tool_calls)}"
all_tool_calls.append({ all_tool_calls.append({
"index": len(all_tool_calls), # Add index to differentiate multiple tool calls "index": len(all_tool_calls),
"id": f"call_{int(time.time() * 1000000)}_{len(all_tool_calls)}", "id": fc_id,
"type": "function", "type": "function",
"function": { "function": {
"name": fc.get("name"), "name": fc.get("name"),

View File

@@ -673,6 +673,9 @@ def _handle_linkai_stream_response(self, base_url, headers, body):
} }
return return
# Forward SSE JSON as-is so extensions (e.g. delta._gemini_raw_parts
# for Gemini via LinkAI) reach agent_stream and are stored on assistant
# messages for the next request. Standard OpenAI fields are unchanged.
yield chunk yield chunk
except Exception as e: except Exception as e: