Skip to content

Commit dcc99e6

Browse files
elecvoid243Soulter
andauthored
feat: 为ChatUI添加指令候选功能 (AstrBotDevs#8279)
* feature: add command suggestion for ChatUI * feat(chat): add focus functionality to chat input after sending messages * feat(llm): add error handling for LLM provider selection failures * feat(command-suggestion): enhance command filtering and sorting logic --------- Co-authored-by: Soulter <905617992@qq.com>
1 parent fd4fe84 commit dcc99e6

10 files changed

Lines changed: 496 additions & 7 deletions

File tree

astrbot/core/astr_main_agent.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@
114114
)
115115
from astrbot.core.utils.string_utils import normalize_and_dedupe_strings
116116

117+
LLM_ERROR_MESSAGE_EXTRA_KEY = "_llm_error_message"
118+
117119

118120
@dataclass(slots=True)
119121
class MainAgentBuildConfig:
@@ -183,25 +185,39 @@ class MainAgentBuildResult:
183185
reset_coro: Coroutine | None = None
184186

185187

188+
def _set_llm_error_message(event: AstrMessageEvent, message: str) -> None:
189+
event.set_extra(LLM_ERROR_MESSAGE_EXTRA_KEY, message)
190+
191+
186192
def _select_provider(
187193
event: AstrMessageEvent, plugin_context: Context
188194
) -> Provider | None:
189195
"""Select chat provider for the event."""
190196
sel_provider = event.get_extra("selected_provider")
191197
if sel_provider and isinstance(sel_provider, str):
192198
provider = plugin_context.get_provider_by_id(sel_provider)
193-
if not provider:
199+
if provider is None:
194200
logger.error("未找到指定的提供商: %s。", sel_provider)
201+
_set_llm_error_message(
202+
event,
203+
f"LLM 请求失败:未找到指定的提供商 `{sel_provider}`。请检查提供商配置或重新选择可用模型。",
204+
)
205+
return None
195206
if not isinstance(provider, Provider):
196207
logger.error(
197208
"选择的提供商类型无效(%s),跳过 LLM 请求处理。", type(provider)
198209
)
210+
_set_llm_error_message(
211+
event,
212+
f"LLM 请求失败:选择的提供商类型无效({type(provider).__name__}),已跳过本次请求。",
213+
)
199214
return None
200215
return provider
201216
try:
202217
return plugin_context.get_using_provider(umo=event.unified_msg_origin)
203218
except ValueError as exc:
204219
logger.error("Error occurred while selecting provider: %s", exc)
220+
_set_llm_error_message(event, f"LLM 请求失败:{exc}")
205221
return None
206222

207223

@@ -1192,6 +1208,11 @@ async def build_main_agent(
11921208
provider = provider or _select_provider(event, plugin_context)
11931209
if provider is None:
11941210
logger.info("未找到任何对话模型(提供商),跳过 LLM 请求处理。")
1211+
if not event.get_extra(LLM_ERROR_MESSAGE_EXTRA_KEY):
1212+
_set_llm_error_message(
1213+
event,
1214+
"LLM 请求失败:未找到任何可用的对话模型(提供商)。请先在 WebUI 中配置并启用可用模型。",
1215+
)
11951216
return None
11961217

11971218
if req is None:

astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
)
1515
from astrbot.core.agent.response import AgentStats
1616
from astrbot.core.astr_main_agent import (
17+
LLM_ERROR_MESSAGE_EXTRA_KEY,
1718
MainAgentBuildConfig,
1819
MainAgentBuildResult,
1920
build_main_agent,
@@ -151,6 +152,11 @@ async def initialize(self, ctx: PipelineContext) -> None:
151152
max_quoted_fallback_images=settings.get("max_quoted_fallback_images", 20),
152153
)
153154

155+
async def _send_llm_error_message(
156+
self, event: AstrMessageEvent, message: object
157+
) -> None:
158+
await event.send(MessageChain().message(str(message)))
159+
154160
async def process(
155161
self, event: AstrMessageEvent, provider_wake_prefix: str
156162
) -> AsyncGenerator[None, None]:
@@ -219,6 +225,13 @@ async def process(
219225
)
220226

221227
if build_result is None:
228+
if llm_error_message := event.get_extra(
229+
LLM_ERROR_MESSAGE_EXTRA_KEY
230+
):
231+
await self._send_llm_error_message(
232+
event,
233+
llm_error_message,
234+
)
222235
return
223236

224237
agent_runner = build_result.agent_runner
@@ -229,10 +242,12 @@ async def process(
229242
api_base = provider.provider_config.get("api_base", "")
230243
for host in decoded_blocked:
231244
if host in api_base:
232-
logger.error(
233-
"Provider API base %s is blocked due to security reasons. Please use another ai provider.",
234-
api_base,
245+
error_message = (
246+
f"LLM 请求失败:Provider API base `{api_base}` "
247+
"因安全原因被拦截,请更换可用的 AI 提供商。"
235248
)
249+
logger.error(error_message)
250+
await self._send_llm_error_message(event, error_message)
236251
return
237252

238253
stream_to_general = (

dashboard/src/components/chat/Chat.vue

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,7 @@ async function startNewChat() {
841841
replyTarget.value = null;
842842
newChat();
843843
closeMobileSidebar();
844+
await focusChatInput();
844845
}
845846
846847
function openCreateProjectDialog() {
@@ -975,6 +976,7 @@ async function selectSession(sessionId: string, pushRoute = true) {
975976
}
976977
scrollToBottom();
977978
closeMobileSidebar();
979+
await focusChatInput();
978980
}
979981
980982
async function sendCurrentMessage() {
@@ -1032,6 +1034,7 @@ async function sendCurrentMessage() {
10321034
console.error("Failed to send message:", error);
10331035
} finally {
10341036
sending.value = false;
1037+
await focusChatInput();
10351038
}
10361039
}
10371040
@@ -1326,6 +1329,13 @@ function scrollToBottom() {
13261329
});
13271330
}
13281331
1332+
async function focusChatInput() {
1333+
await nextTick();
1334+
window.requestAnimationFrame(() => {
1335+
inputRef.value?.focusInput();
1336+
});
1337+
}
1338+
13291339
async function stopCurrentSession() {
13301340
if (!currSessionId.value) return;
13311341
try {
@@ -1487,6 +1497,9 @@ function toggleTheme() {
14871497
align-items: center;
14881498
gap: 8px;
14891499
padding: 8px 12px;
1500+
padding-right: 68px;
1501+
position: relative;
1502+
box-sizing: border-box;
14901503
cursor: pointer;
14911504
text-align: left;
14921505
}
@@ -1511,15 +1524,24 @@ function toggleTheme() {
15111524
}
15121525
15131526
.session-actions {
1514-
display: none;
1527+
display: flex;
15151528
align-items: center;
15161529
gap: 2px;
15171530
flex-shrink: 0;
1531+
opacity: 0;
1532+
pointer-events: none;
1533+
position: absolute;
1534+
right: 8px;
1535+
top: 50%;
1536+
transform: translateY(-50%);
1537+
visibility: hidden;
15181538
}
15191539
15201540
.session-item:hover .session-actions,
15211541
.session-item:focus-within .session-actions {
1522-
display: flex;
1542+
opacity: 1;
1543+
pointer-events: auto;
1544+
visibility: visible;
15231545
}
15241546
15251547
.session-action-btn {

0 commit comments

Comments
 (0)