feat: loading animation + tool descriptions in system prompt
1. Loading indicator: three-dot bouncing animation appears after sending a message and disappears when server starts responding. 2. Tool descriptions: resolve_skill_routing now appends available tools (name + description + parameters) to the system prompt so the LLM knows what tools it can call.
This commit is contained in:
parent
55421dd126
commit
44f19fcf14
|
|
@ -210,9 +210,31 @@ async def resolve_skill_routing(
|
|||
result.model = default_model
|
||||
result.agent_name = default_agent_name
|
||||
|
||||
# Append available tools to system prompt so LLM knows what it can call
|
||||
if result.tools:
|
||||
tools_desc = _build_tools_description(result.tools)
|
||||
if result.system_prompt:
|
||||
result.system_prompt += f"\n\n## Available Tools\n{tools_desc}"
|
||||
else:
|
||||
result.system_prompt = f"## Available Tools\n{tools_desc}"
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _build_tools_description(tools: list) -> str:
|
||||
"""Build a text description of tools for the system prompt."""
|
||||
lines = []
|
||||
for tool in tools:
|
||||
desc = getattr(tool, 'description', '')
|
||||
lines.append(f"- **{tool.name}**: {desc}")
|
||||
schema = getattr(tool, 'input_schema', None)
|
||||
if schema and "properties" in schema:
|
||||
params = list(schema["properties"].keys())
|
||||
if params:
|
||||
lines[-1] += f" (parameters: {', '.join(params)})"
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CostAwareRouter - 三层成本感知路由
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -164,6 +164,14 @@ html,body{height:100%;font-family:var(--font);background:var(--bg);color:var(--t
|
|||
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
||||
@keyframes slideInRight{from{opacity:0;transform:translateX(12px)}to{opacity:1;transform:translateX(0)}}
|
||||
|
||||
/* ── Loading indicator ───────────────────────────────────────── */
|
||||
.loading-msg{display:flex;flex-direction:column;max-width:72%;align-self:flex-start;animation:msgIn .35s cubic-bezier(.16,1,.3,1)}
|
||||
.loading-msg .loading-bubble{padding:8px 20px;background:var(--surface);border:1px solid var(--border-light);border-radius:var(--radius-lg) var(--radius-lg) var(--radius-lg) var(--radius-sm);display:inline-flex;align-items:center;gap:6px;box-shadow:var(--shadow-xs)}
|
||||
.loading-msg .loading-dot{width:8px;height:8px;border-radius:50%;background:var(--text3);animation:loadingDot 1.4s infinite ease-in-out}
|
||||
.loading-msg .loading-dot:nth-child(2){animation-delay:.2s}
|
||||
.loading-msg .loading-dot:nth-child(3){animation-delay:.4s}
|
||||
@keyframes loadingDot{0%,80%,100%{transform:scale(.4);opacity:.3}40%{transform:scale(1);opacity:1}}
|
||||
|
||||
/* ── Mobile ──────────────────────────────────────────────────── */
|
||||
@media(max-width:768px){
|
||||
.sidebar{position:fixed;left:-100%;z-index:10;transition:left .3s cubic-bezier(.16,1,.3,1);width:85vw;max-width:320px;box-shadow:var(--shadow-lg)}
|
||||
|
|
@ -262,6 +270,7 @@ let activeSessionId = null;
|
|||
let ws = null;
|
||||
let isStreaming = false;
|
||||
let currentAgentBubble = null;
|
||||
let loadingEl = null;
|
||||
let skills = [];
|
||||
const API = '/api/v1/chat';
|
||||
const SKILLS_API = '/api/v1/skills';
|
||||
|
|
@ -380,7 +389,12 @@ function handleWsMessage(msg) {
|
|||
case 'connected':
|
||||
setConnStatus('已连接', true);
|
||||
break;
|
||||
case 'routing':
|
||||
removeLoading();
|
||||
appendStep(`路由: ${msg.skill || 'default'} (${msg.method}, ${Math.round((msg.confidence || 0) * 100)}%)`);
|
||||
break;
|
||||
case 'token':
|
||||
removeLoading();
|
||||
if (!currentAgentBubble) {
|
||||
currentAgentBubble = appendMessage('agent', '');
|
||||
isStreaming = true;
|
||||
|
|
@ -390,6 +404,7 @@ function handleWsMessage(msg) {
|
|||
scrollToBottom();
|
||||
break;
|
||||
case 'final_answer':
|
||||
removeLoading();
|
||||
if (currentAgentBubble) {
|
||||
const current = currentAgentBubble.textContent || '';
|
||||
const final = msg.content || '';
|
||||
|
|
@ -405,16 +420,19 @@ function handleWsMessage(msg) {
|
|||
scrollToBottom();
|
||||
break;
|
||||
case 'step':
|
||||
removeLoading();
|
||||
if (msg.data?.event_type === 'tool_call') {
|
||||
appendStep(`使用工具: ${msg.data?.data?.tool_name || 'tool'}`);
|
||||
}
|
||||
break;
|
||||
case 'skill_match':
|
||||
removeLoading();
|
||||
if (msg.data?.skill) {
|
||||
appendStep(`技能: ${msg.data.skill} (${msg.data.method}, ${Math.round((msg.data.confidence || 0) * 100)}%)`);
|
||||
}
|
||||
break;
|
||||
case 'error':
|
||||
removeLoading();
|
||||
appendMessage('agent', `[错误] ${msg.data?.message || '未知错误'}`);
|
||||
currentAgentBubble = null;
|
||||
isStreaming = false;
|
||||
|
|
@ -423,6 +441,27 @@ function handleWsMessage(msg) {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Loading indicator ──────────────────────────────────────────
|
||||
function showLoading() {
|
||||
if (loadingEl) return;
|
||||
hideWelcome();
|
||||
const container = document.getElementById('messages');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'loading-msg';
|
||||
div.id = 'loadingIndicator';
|
||||
div.innerHTML = '<div class="loading-bubble"><div class="loading-dot"></div><div class="loading-dot"></div><div class="loading-dot"></div></div>';
|
||||
container.appendChild(div);
|
||||
loadingEl = div;
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function removeLoading() {
|
||||
if (loadingEl) {
|
||||
loadingEl.remove();
|
||||
loadingEl = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Send message ───────────────────────────────────────────────
|
||||
async function sendMessage() {
|
||||
const input = document.getElementById('input');
|
||||
|
|
@ -464,6 +503,7 @@ async function sendMessage() {
|
|||
currentAgentBubble = null;
|
||||
isStreaming = true;
|
||||
updateSendBtn();
|
||||
showLoading();
|
||||
}
|
||||
|
||||
function handleKey(e) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue