From 3c53e89b431f6a08719f692865c068cbce2d3dcf Mon Sep 17 00:00:00 2001 From: MT-Fire <798521692@qq.com> Date: Wed, 11 Mar 2026 11:32:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9Eqwen3.5=E7=B3=BB?= =?UTF-8?q?=E5=88=97=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/adapters/dashscope_adapter.py | 473 +++++++++++++++++++++----- server/adapters/glm_adapter.py | 11 + server/adapters/registry.py | 12 +- src/components/chat/WelcomeScreen.vue | 4 +- src/components/input/ChatInput.vue | 5 +- 5 files changed, 413 insertions(+), 92 deletions(-) diff --git a/server/adapters/dashscope_adapter.py b/server/adapters/dashscope_adapter.py index a6c94b7..00f85de 100644 --- a/server/adapters/dashscope_adapter.py +++ b/server/adapters/dashscope_adapter.py @@ -14,12 +14,40 @@ from core import get_logger logger = get_logger() +# 支持深度思考的模型 +THINKING_MODELS = {"qwen3-max", "qwen3.5-plus"} + +# 需要使用多模态接口的模型(qwen3.5 系列) +MULTIMODAL_API_MODELS = {"qwen3.5-plus", "qwen3.5-flash"} + # 百炼模型配置 DASHSCOPE_MODELS = [ ModelInfo( - id="qwen-max", - name="通义千问 Max", - description="最强大的模型", + id="qwen3-max", + name="Qwen3-Max", + description="千问系列效果最好的模型,适合复杂、多步骤的任务。", + max_tokens=8192, + provider="Aliyun", + supports_thinking=True, + supports_web_search=False, + supports_vision=False, + supports_files=False, + ), + ModelInfo( + id="qwen3.5-plus", + name="Qwen3.5-Plus", + description="能力均衡,推理效果、成本和速度介于千问Max和千问Flash之间,适合中等复杂任务。", + max_tokens=8192, + provider="Aliyun", + supports_thinking=True, + supports_web_search=False, + supports_vision=True, + supports_files=False, + ), + ModelInfo( + id="qwen3.5-flash", + name="Qwen3.5-Flash", + description="千问系列速度最快、成本极低的模型,适合简单任务。千问Flash采用灵活的阶梯定价,相比千问Turbo计费更合理。", max_tokens=8192, provider="Aliyun", supports_thinking=False, @@ -27,28 +55,6 @@ DASHSCOPE_MODELS = [ supports_vision=False, supports_files=False, ), - ModelInfo( - id="qwen-plus", - name="通义千问 Plus", - description="能力均衡", - max_tokens=8192, - provider="Aliyun", - supports_thinking=True, - supports_web_search=False, - supports_vision=False, - supports_files=False, - ), - ModelInfo( - id="qwen-turbo", - name="通义千问 Turbo", - description="速度更快、成本更低", - max_tokens=8192, - provider="Aliyun", - supports_thinking=True, - supports_web_search=False, - supports_vision=False, - supports_files=False, - ), ModelInfo( id="qwen-vl-max", name="通义万相 VL-Max", @@ -60,17 +66,6 @@ DASHSCOPE_MODELS = [ supports_vision=True, supports_files=False, ), - ModelInfo( - id="qwen-vl-plus", - name="通义万相 VL-Plus", - description="支持视觉理解的多模态模型", - max_tokens=8192, - provider="Aliyun", - supports_thinking=False, - supports_web_search=False, - supports_vision=True, - supports_files=False, - ), ] @@ -89,6 +84,14 @@ class DashScopeAdapter(BaseAdapter): """获取 API Key""" return os.getenv("ALIYUN_API_KEY") or os.getenv("DASHSCOPE_API_KEY", "") + def _needs_multimodal_api(self, model: str) -> bool: + """检查模型是否需要使用多模态 API""" + return model.lower() in MULTIMODAL_API_MODELS + + def _supports_thinking(self, model: str) -> bool: + """检查模型是否支持深度思考""" + return model.lower() in THINKING_MODELS + def list_models(self) -> List[ModelInfo]: return DASHSCOPE_MODELS @@ -104,6 +107,7 @@ class DashScopeAdapter(BaseAdapter): logger.info(f" - temperature: {request.temperature}") logger.info(f" - max_tokens: {request.max_tokens}") logger.info(f" - files: {request.files}") + logger.info(f" - deep_thinking: {request.deep_thinking}") logger.info( f" - messages: {json.dumps(request.messages, ensure_ascii=False, indent=2)}" ) @@ -112,7 +116,11 @@ class DashScopeAdapter(BaseAdapter): has_multimodal = self._has_multimodal_content(request) logger.info(f" - has_multimodal: {has_multimodal}") - if has_multimodal: + # 检查是否需要使用多模态接口(qwen3.5 系列) + needs_multimodal_api = self._needs_multimodal_api(request.model) + logger.info(f" - needs_multimodal_api: {needs_multimodal_api}") + + if has_multimodal or needs_multimodal_api: return await self._multimodal_chat(request) else: return await self._text_chat(request) @@ -136,6 +144,9 @@ class DashScopeAdapter(BaseAdapter): # 转换消息格式 messages = self._build_text_messages(request) + logger.info(f"[DashScope] 文本聊天 - 转换后的消息:") + logger.info(f" - messages_count: {len(messages)}") + logger.info(f" - messages: {json.dumps(messages, ensure_ascii=False, indent=2)}") if request.stream: return self._stream_text_chat(messages, request) @@ -163,26 +174,97 @@ class DashScopeAdapter(BaseAdapter): """流式文本聊天""" logger.info(f"[DashScope] 开始流式文本响应...") + # 检查是否启用深度思考 + thinking_enabled = request.deep_thinking and self._supports_thinking(request.model) + logger.info(f"[DashScope] 深度思考: {thinking_enabled} (request={request.deep_thinking}, supports={self._supports_thinking(request.model)})") + def generator(): from utils.helpers import generate_unique_id, get_current_timestamp from dashscope import Generation full_content = "" + full_reasoning = "" chunk_count = 0 - responses = Generation.call( - model=request.model, - messages=messages, - stream=True, - temperature=request.temperature, - max_tokens=request.max_tokens, - result_format="message", - ) + error_occurred = False + + # 构建 API 调用参数 + api_params = { + "model": request.model, + "messages": messages, + "stream": True, + "temperature": request.temperature, + "max_tokens": request.max_tokens, + "result_format": "message", + } + + # 添加深度思考参数 + if thinking_enabled: + api_params["enable_thinking"] = True + + # 打印 API 调用参数 + logger.info(f"[DashScope] API 调用参数:") + logger.info(f" - model: {api_params['model']}") + logger.info(f" - stream: {api_params['stream']}") + logger.info(f" - temperature: {api_params['temperature']}") + logger.info(f" - max_tokens: {api_params['max_tokens']}") + logger.info(f" - result_format: {api_params['result_format']}") + if thinking_enabled: + logger.info(f" - enable_thinking: True") + + try: + responses = Generation.call(**api_params) + except Exception as e: + error_occurred = True + logger.error(f"[DashScope] API 调用异常: {str(e)}") + import traceback + logger.error(traceback.format_exc()) + # 返回错误响应 + error_data = { + "id": f"chatcmpl-{generate_unique_id()}", + "object": "chat.completion.chunk", + "created": get_current_timestamp(), + "model": request.model, + "choices": [{ + "index": 0, + "delta": {"content": f"API 调用失败: {str(e)}"}, + "finish_reason": "stop", + }], + } + yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n" + yield "data: [DONE]\n\n" + return for resp in responses: if resp.status_code == 200: chunk_count += 1 - content = resp.output.choices[0].message.content + choice = resp.output.choices[0] + + # 处理深度思考内容(reasoning_content) + reasoning_content = getattr(choice.message, "reasoning_content", None) + if reasoning_content: + # 计算增量 + if len(reasoning_content) > len(full_reasoning): + delta_reasoning = reasoning_content[len(full_reasoning):] + full_reasoning = reasoning_content + data = { + "id": f"chatcmpl-{generate_unique_id()}", + "object": "chat.completion.chunk", + "created": get_current_timestamp(), + "model": request.model, + "choices": [ + { + "index": 0, + "delta": {"reasoning_content": delta_reasoning}, + "finish_reason": None, + } + ], + } + yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n" + continue + + # 处理普通内容 + content = choice.message.content if content and len(content) > len(full_content): # DashScope 流式响应返回完整内容,计算增量 delta = content[len(full_content) :] @@ -201,6 +283,9 @@ class DashScopeAdapter(BaseAdapter): ], } yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n" + else: + # 记录非200响应 + logger.warning(f"[DashScope] 非200响应: status_code={resp.status_code}, code={resp.code}, message={resp.message}") finish = { "id": f"chatcmpl-{generate_unique_id()}", @@ -216,6 +301,8 @@ class DashScopeAdapter(BaseAdapter): logger.info(f"[DashScope] 流式文本响应完成:") logger.info(f" - chunks: {chunk_count}") logger.info(f" - content_length: {len(full_content)} 字符") + if full_reasoning: + logger.info(f" - reasoning_length: {len(full_reasoning)} 字符") logger.info( f" - content_preview: {full_content[:200]}..." if len(full_content) > 200 @@ -230,17 +317,57 @@ class DashScopeAdapter(BaseAdapter): from dashscope import Generation - resp = Generation.call( - model=request.model, - messages=messages, - stream=False, - temperature=request.temperature, - max_tokens=request.max_tokens, - result_format="message", - ) + # 检查是否启用深度思考 + thinking_enabled = request.deep_thinking and self._supports_thinking(request.model) + logger.info(f"[DashScope] 深度思考: {thinking_enabled} (request={request.deep_thinking}, supports={self._supports_thinking(request.model)})") + + # 构建 API 调用参数 + api_params = { + "model": request.model, + "messages": messages, + "stream": False, + "temperature": request.temperature, + "max_tokens": request.max_tokens, + "result_format": "message", + } + + # 添加深度思考参数 + if thinking_enabled: + api_params["enable_thinking"] = True + + # 打印 API 调用参数 + logger.info(f"[DashScope] API 调用参数:") + logger.info(f" - model: {api_params['model']}") + logger.info(f" - stream: {api_params['stream']}") + logger.info(f" - temperature: {api_params['temperature']}") + logger.info(f" - max_tokens: {api_params['max_tokens']}") + logger.info(f" - result_format: {api_params['result_format']}") + if thinking_enabled: + logger.info(f" - enable_thinking: True") + + try: + resp = Generation.call(**api_params) + except Exception as e: + logger.error(f"[DashScope] API 调用异常: {str(e)}") + import traceback + logger.error(traceback.format_exc()) + return JSONResponse( + status_code=500, + content={"error": f"DashScope API 调用异常: {str(e)}"}, + ) if resp.status_code == 200: - content = resp.output.choices[0].message.content + message = resp.output.choices[0].message + content = message.content or "" + + # 构建响应消息 + response_message = {"role": "assistant", "content": content} + + # 处理深度思考内容 + reasoning_content = getattr(message, "reasoning_content", None) + if reasoning_content: + response_message["reasoning_content"] = reasoning_content + response = { "id": f"chatcmpl-{generate_unique_id()}", "object": "chat.completion", @@ -249,7 +376,7 @@ class DashScopeAdapter(BaseAdapter): "choices": [ { "index": 0, - "message": {"role": "assistant", "content": content}, + "message": response_message, "finish_reason": "stop", } ], @@ -263,8 +390,11 @@ class DashScopeAdapter(BaseAdapter): } # 打印响应结果 - logger.info(f"[DashScope] 响应结果:") + logger.info(f"[DashScope] 响应成功:") + logger.info(f" - status_code: {resp.status_code}") logger.info(f" - content_length: {len(content)} 字符") + if reasoning_content: + logger.info(f" - reasoning_length: {len(reasoning_content)} 字符") logger.info( f" - content_preview: {content[:200]}..." if len(content) > 200 @@ -275,7 +405,10 @@ class DashScopeAdapter(BaseAdapter): return JSONResponse(content=response) - logger.error(f"[DashScope] 请求失败: {resp.code} - {resp.message}") + logger.error(f"[DashScope] 请求失败:") + logger.error(f" - status_code: {resp.status_code}") + logger.error(f" - code: {resp.code}") + logger.error(f" - message: {resp.message}") return JSONResponse( status_code=500, content={"error": f"DashScope Error: {resp.code} - {resp.message}"}, @@ -288,13 +421,20 @@ class DashScopeAdapter(BaseAdapter): dashscope.api_key = self._get_api_key() + logger.info(f"[DashScope] 开始多模态聊天...") + # 转换消息格式 messages = self._build_multimodal_messages(request) + logger.info(f"[DashScope] 多模态消息转换完成:") + logger.info(f" - messages_count: {len(messages)}") + logger.info(f" - messages: {json.dumps(messages, ensure_ascii=False, indent=2)}") # 选择多模态模型 model = request.model if "qwen-" in model and "vl" not in model: + original_model = model model = model.replace("qwen-", "qwen-vl-") + logger.info(f"[DashScope] 模型自动切换: {original_model} -> {model}") if request.stream: return self._stream_multimodal_chat(messages, model, request) @@ -338,6 +478,8 @@ class DashScopeAdapter(BaseAdapter): else: img_url = "" + logger.info(f"[DashScope] 原始图片URL: {img_url}") + # 转换 http URL 为 file:// 格式(如果是本地文件) if img_url.startswith(("http://", "https://")): from urllib.parse import urlparse @@ -350,42 +492,133 @@ class DashScopeAdapter(BaseAdapter): img_url = f"file://{'/'.join(path_parts[uploads_idx:])}" except ValueError: pass - elif not img_url.startswith("file://"): + elif not img_url.startswith("file://") and not img_url.startswith(("http://", "https://")): img_url = f"file://{img_url}" + logger.info(f"[DashScope] 转换后图片URL: {img_url}") + return img_url def _stream_multimodal_chat( self, messages: List[Dict], model: str, request: ChatCompletionRequest ): """流式多模态聊天""" + logger.info(f"[DashScope] 开始流式多模态响应...") + logger.info(f" - model: {model}") + logger.info(f" - max_tokens: {request.max_tokens}") + logger.info(f" - temperature: {request.temperature}") + + # 检查是否启用深度思考 + thinking_enabled = request.deep_thinking and self._supports_thinking(model) + logger.info(f"[DashScope] 深度思考: {thinking_enabled} (request={request.deep_thinking}, supports={self._supports_thinking(model)})") def generator(): from utils.helpers import generate_unique_id, get_current_timestamp from dashscope import MultiModalConversation - responses = MultiModalConversation.call( - model=model, - messages=messages, - stream=True, - max_tokens=request.max_tokens, - temperature=request.temperature, - ) - full_content = "" + full_reasoning = "" + chunk_count = 0 + error_occurred = False + + # 打印 API 调用参数 + api_params = { + "model": model, + "messages": messages, + "stream": True, + "max_tokens": request.max_tokens, + "temperature": request.temperature, + } + + # 添加深度思考参数 + if thinking_enabled: + api_params["enable_thinking"] = True + + logger.info(f"[DashScope] 多模态 API 调用参数:") + logger.info(f" - model: {api_params['model']}") + logger.info(f" - stream: {api_params['stream']}") + logger.info(f" - max_tokens: {api_params['max_tokens']}") + logger.info(f" - temperature: {api_params['temperature']}") + if thinking_enabled: + logger.info(f" - enable_thinking: True") + logger.info(f" - messages: {json.dumps(messages, ensure_ascii=False, indent=2)}") + + try: + responses = MultiModalConversation.call(**api_params) + except Exception as e: + error_occurred = True + logger.error(f"[DashScope] 多模态 API 调用异常: {str(e)}") + import traceback + logger.error(traceback.format_exc()) + error_data = { + "id": f"chatcmpl-{generate_unique_id()}", + "object": "chat.completion.chunk", + "created": get_current_timestamp(), + "model": model, + "choices": [{ + "index": 0, + "delta": {"content": f"API 调用失败: {str(e)}"}, + "finish_reason": "stop", + }], + } + yield f"data: {json.dumps(error_data, ensure_ascii=False)}\n\n" + yield "data: [DONE]\n\n" + return + for resp in responses: + chunk_count += 1 + logger.info(f"[DashScope] === chunk {chunk_count} ===") + if resp.status_code == 200: try: - content_items = resp.output.choices[0]["message"]["content"] + # 打印原始响应结构 + logger.info(f" - resp.status_code: {resp.status_code}") + logger.info(f" - resp.output: {resp.output}") + + choice = resp.output.choices[0] + message = choice["message"] + + # 处理深度思考内容(reasoning_content) + # 多模态 API 返回的 reasoning_content 也是独立的片段 + reasoning_content = message.get("reasoning_content", "") + if reasoning_content: + delta_reasoning = reasoning_content + full_reasoning += reasoning_content + logger.info(f" - reasoning_delta: {delta_reasoning}") + + data = { + "id": f"chatcmpl-{generate_unique_id()}", + "object": "chat.completion.chunk", + "created": get_current_timestamp(), + "model": model, + "choices": [ + { + "index": 0, + "delta": {"reasoning_content": delta_reasoning}, + "finish_reason": None, + } + ], + } + yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n" + continue + + # 处理普通内容 + content_items = message.get("content", []) text = "" for item in content_items: if isinstance(item, dict) and "text" in item: text += item["text"] - if len(text) > len(full_content): - delta = text[len(full_content) :] - full_content = text + # 打印每个 chunk 的内容 + logger.info(f" - text_len: {len(text)}, full_len: {len(full_content)}") + logger.info(f" - text: {text}") + + # 多模态 API 返回的 content 是独立的片段(不是累积的),直接作为 delta + if text: + delta = text + full_content += text + logger.info(f" - delta: {delta}") data = { "id": f"chatcmpl-{generate_unique_id()}", @@ -401,8 +634,10 @@ class DashScopeAdapter(BaseAdapter): ], } yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n" - except (KeyError, IndexError, TypeError): - pass + except (KeyError, IndexError, TypeError) as e: + logger.warning(f"[DashScope] 解析多模态响应异常: {str(e)}") + else: + logger.warning(f"[DashScope] 非200响应: status_code={resp.status_code}, code={resp.code}, message={resp.message}") finish = { "id": f"chatcmpl-{generate_unique_id()}", @@ -414,6 +649,18 @@ class DashScopeAdapter(BaseAdapter): yield f"data: {json.dumps(finish, ensure_ascii=False)}\n\n" yield "data: [DONE]\n\n" + # 打印流式响应结果 + logger.info(f"[DashScope] 流式多模态响应完成:") + logger.info(f" - chunks: {chunk_count}") + logger.info(f" - content_length: {len(full_content)} 字符") + if full_reasoning: + logger.info(f" - reasoning_length: {len(full_reasoning)} 字符") + logger.info( + f" - content_preview: {full_content[:200]}..." + if len(full_content) > 200 + else f" - content: {full_content}" + ) + return StreamingResponse(generator(), media_type="text/event-stream") def _sync_multimodal_chat( @@ -424,22 +671,64 @@ class DashScopeAdapter(BaseAdapter): from dashscope import MultiModalConversation - resp = MultiModalConversation.call( - model=model, - messages=messages, - stream=False, - max_tokens=request.max_tokens, - temperature=request.temperature, - ) + # 检查是否启用深度思考 + thinking_enabled = request.deep_thinking and self._supports_thinking(model) + logger.info(f"[DashScope] 深度思考: {thinking_enabled} (request={request.deep_thinking}, supports={self._supports_thinking(model)})") + + logger.info(f"[DashScope] 开始非流式多模态响应...") + logger.info(f" - model: {model}") + logger.info(f" - max_tokens: {request.max_tokens}") + logger.info(f" - temperature: {request.temperature}") + + # 打印 API 调用参数 + api_params = { + "model": model, + "messages": messages, + "stream": False, + "max_tokens": request.max_tokens, + "temperature": request.temperature, + } + + # 添加深度思考参数 + if thinking_enabled: + api_params["enable_thinking"] = True + + logger.info(f"[DashScope] 多模态 API 调用参数:") + logger.info(f" - model: {api_params['model']}") + logger.info(f" - stream: {api_params['stream']}") + logger.info(f" - max_tokens: {api_params['max_tokens']}") + logger.info(f" - temperature: {api_params['temperature']}") + if thinking_enabled: + logger.info(f" - enable_thinking: True") + + try: + resp = MultiModalConversation.call(**api_params) + except Exception as e: + logger.error(f"[DashScope] 多模态 API 调用异常: {str(e)}") + import traceback + logger.error(traceback.format_exc()) + return JSONResponse( + status_code=500, + content={"error": f"DashScope API 调用异常: {str(e)}"}, + ) if resp.status_code == 200: try: - content_items = resp.output.choices[0]["message"]["content"] + message = resp.output.choices[0]["message"] + content_items = message.get("content", []) text = "" for item in content_items: if isinstance(item, dict) and "text" in item: text += item["text"] + # 构建响应消息 + response_message = {"role": "assistant", "content": text} + + # 处理深度思考内容 + reasoning_content = message.get("reasoning_content") + if reasoning_content: + response_message["reasoning_content"] = reasoning_content + response = { "id": f"chatcmpl-{generate_unique_id()}", "object": "chat.completion", @@ -448,18 +737,38 @@ class DashScopeAdapter(BaseAdapter): "choices": [ { "index": 0, - "message": {"role": "assistant", "content": text}, + "message": response_message, "finish_reason": "stop", } ], } + + # 打印响应结果 + logger.info(f"[DashScope] 多模态响应成功:") + logger.info(f" - status_code: {resp.status_code}") + logger.info(f" - content_length: {len(text)} 字符") + if reasoning_content: + logger.info(f" - reasoning_length: {len(reasoning_content)} 字符") + logger.info( + f" - content_preview: {text[:200]}..." + if len(text) > 200 + else f" - content: {text}" + ) + return JSONResponse(content=response) except (KeyError, IndexError, TypeError) as e: + logger.error(f"[DashScope] 解析多模态响应异常: {str(e)}") + import traceback + logger.error(traceback.format_exc()) return JSONResponse( status_code=500, content={"error": f"Parse error: {str(e)}"}, ) + logger.error(f"[DashScope] 多模态请求失败:") + logger.error(f" - status_code: {resp.status_code}") + logger.error(f" - code: {resp.code}") + logger.error(f" - message: {resp.message}") return JSONResponse( status_code=500, content={"error": f"DashScope Error: {resp.code} - {resp.message}"}, diff --git a/server/adapters/glm_adapter.py b/server/adapters/glm_adapter.py index b752a8c..1c9c962 100644 --- a/server/adapters/glm_adapter.py +++ b/server/adapters/glm_adapter.py @@ -17,6 +17,17 @@ logger = get_logger() # GLM 模型配置 GLM_MODELS = [ + ModelInfo( + id="glm-5", + name="GLM-5", + description="Coding与长程Agent能力SOTA", + max_tokens=128000, + provider="ZhipuAI", + supports_thinking=True, + supports_web_search=False, + supports_vision=True, + supports_files=True, + ), ModelInfo( id="glm-4.6v", name="GLM-4.6V(推荐)", diff --git a/server/adapters/registry.py b/server/adapters/registry.py index a44dd1a..bf97d5a 100644 --- a/server/adapters/registry.py +++ b/server/adapters/registry.py @@ -11,15 +11,15 @@ from .base import BaseAdapter # 模型前缀到平台名称的映射 MODEL_PREFIX_MAP = { # 智谱 GLM - "glm-": "glm", + "glm": "glm", # 阿里云百炼(Qwen 系列) - "qwen-": "dashscope", + "qwen": "dashscope", # OpenAI - "gpt-": "openai", - "o1-": "openai", - "o3-": "openai", + "gpt": "openai", + "o1": "openai", + "o3": "openai", # Deepseek - "deepseek-": "deepseek", + "deepseek": "deepseek", } # 已注册的适配器实例 diff --git a/src/components/chat/WelcomeScreen.vue b/src/components/chat/WelcomeScreen.vue index ff357a9..620554b 100644 --- a/src/components/chat/WelcomeScreen.vue +++ b/src/components/chat/WelcomeScreen.vue @@ -8,9 +8,9 @@
-

Kexue AI 智能助手

+

大学教育助手

- 大学生用GPT,把自己学废了? Study模式拒绝直接给答案,引导学生思考。 + 等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入等待输入

diff --git a/src/components/input/ChatInput.vue b/src/components/input/ChatInput.vue index 2c43356..09396f4 100644 --- a/src/components/input/ChatInput.vue +++ b/src/components/input/ChatInput.vue @@ -251,7 +251,8 @@ function autoResize() { textarea.style.height = "auto"; const maxHeight = isExpanded.value ? 400 : 160; - textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)}px`; + // 增加1px是为了避免滚动条出现 + textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)+1}px`; } // 处理键盘事件 @@ -631,7 +632,7 @@ onMounted(() => { textarea { width: 100%; - min-height: 24px; + min-height: 25px; max-height: 160px; padding: 8px 0; border: none;