Skip to content

Commit 51e8a97

Browse files
authored
Merge pull request #952 from jupyter-naas/951-featabi-agent-improve-suggestions-ux-in-abiagent
feat(abi-agent): improve suggestions UX in AbiAgent
2 parents 43bcd0e + 9395da1 commit 51e8a97

8 files changed

Lines changed: 2955 additions & 35 deletions

File tree

libs/naas-abi-cli/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libs/naas-abi/naas_abi/agents/AbiAgent.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,29 @@ class AbiAgent(IntentAgent):
6767
- Keep responses concise and factual.
6868
</constraints>
6969
"""
70-
suggestions: list[dict[str, str]] = [
71-
# {
72-
# "label": "Chat with a specific agent",
73-
# "value": "Chat with {{Agent Name}}",
74-
# },
70+
suggestions: list[dict] = [
71+
{
72+
"label": "What can you do?",
73+
"value": "What can you do?",
74+
"description": "Get an overview of all available agents and their capabilities",
75+
},
76+
{
77+
"label": "Find the right agent",
78+
"value": "Find the best agent for my task",
79+
"description": "Let Abi recommend the right agent for your request",
80+
},
81+
{
82+
"label": "Explore my knowledge graph",
83+
"value": "",
84+
"description": "Browse entities and relationships in your data",
85+
"disabled": True,
86+
},
87+
{
88+
"label": "Browse Apps",
89+
"value": "",
90+
"description": "Discover and enable modules from the marketplace",
91+
"cta": "/apps",
92+
},
7593
]
7694

7795
# @staticmethod

libs/naas-abi/naas_abi/apps/nexus/apps/api/app/services/agents/adapters/primary/agents__primary_adapter__FastAPI.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,27 @@ def request_context(current_user: User) -> RequestContext:
111111
)
112112

113113

114-
def _extract_agent_suggestions(agent_cls: type) -> list[dict[str, str]] | None:
114+
def _extract_agent_suggestions(agent_cls: type) -> list[dict] | None:
115115
suggestions = getattr(agent_cls, "suggestions", None)
116116
if not isinstance(suggestions, list):
117117
return None
118118

119-
normalized: list[dict[str, str]] = []
119+
normalized: list[dict] = []
120120
for item in suggestions:
121121
if not isinstance(item, dict):
122122
continue
123123
label = item.get("label")
124124
value = item.get("value")
125-
if isinstance(label, str) and isinstance(value, str):
126-
normalized.append({"label": label, "value": value})
125+
if not isinstance(label, str) or not isinstance(value, str):
126+
continue
127+
entry: dict = {"label": label, "value": value}
128+
if "description" in item:
129+
entry["description"] = item["description"]
130+
if "disabled" in item:
131+
entry["disabled"] = item["disabled"]
132+
if "cta" in item:
133+
entry["cta"] = item["cta"]
134+
normalized.append(entry)
127135
return normalized
128136

129137

libs/naas-abi/naas_abi/apps/nexus/apps/api/app/services/agents/port.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class AgentRecord:
2020
logo_url: str | None
2121
created_at: datetime
2222
updated_at: datetime
23-
suggestions: list[dict[str, str]] | None = None
23+
suggestions: list[dict] | None = None
2424
intents: list[dict[str, str]] | None = None
2525

2626

libs/naas-abi/naas_abi/apps/nexus/apps/api/uv.lock

Lines changed: 2837 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

libs/naas-abi/naas_abi/apps/nexus/apps/web/src/components/chat/chat-interface.tsx

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, { useState, useRef, useEffect, useMemo, useCallback, useLayoutEffe
44
import { createPortal } from 'react-dom';
55
import { Send, Plus, Bot, User, AlertCircle, Brain, ChevronDown, X, ArrowUp, Download, ExternalLink, HardDrive, RefreshCw, Mic, Check, Loader2, Wrench, Copy } from 'lucide-react';
66
import Image from 'next/image';
7+
import { useRouter } from 'next/navigation';
78
import ReactMarkdown from 'react-markdown';
89
import remarkGfm from 'remark-gfm';
910
import { cn } from '@/lib/utils';
@@ -1339,7 +1340,7 @@ export function ChatInterface() {
13391340
if (!stepName) return;
13401341

13411342
const label = stepType === 'call_model'
1342-
? `Calling model`
1343+
? `Calling ${stepName}`
13431344
: `Routing to ${stepName}`;
13441345

13451346
const last = streamToolCalls[streamToolCalls.length - 1];
@@ -1377,7 +1378,7 @@ export function ChatInterface() {
13771378
if (!rawTool) return false;
13781379
const input = getStringValue(payload.input, payload.data) || undefined;
13791380
handleToolStartEvent(rawTool, input);
1380-
streamActivityLine = `${formatToolName(rawTool)}`;
1381+
streamActivityLine = `Tool: ${formatToolName(rawTool)}`;
13811382
hasDetailedActivity = true;
13821383
return true;
13831384
}
@@ -1782,6 +1783,8 @@ export function ChatInterface() {
17821783
logoUrl={selectedAgentData?.logoUrl ?? undefined}
17831784
suggestions={selectedAgentData?.suggestions}
17841785
onSuggestionClick={(prompt) => { setInput(prompt); focusChatInput(); }}
1786+
onSuggestionHover={(value) => setInput(value)}
1787+
onSuggestionLeave={() => setInput('')}
17851788
/>
17861789
) : (
17871790
<div className="mx-auto max-w-3xl space-y-6">
@@ -2231,16 +2234,26 @@ function EmptyState({
22312234
logoUrl,
22322235
suggestions,
22332236
onSuggestionClick,
2237+
onSuggestionHover,
2238+
onSuggestionLeave,
22342239
}: {
22352240
selectedAgentName: string;
22362241
logoUrl?: string | null;
2237-
suggestions?: Array<{ label: string; value: string }>;
2242+
suggestions?: Array<{ label: string; value: string; description?: string; disabled?: boolean; cta?: string }>;
22382243
onSuggestionClick: (prompt: string) => void;
2244+
onSuggestionHover?: (value: string) => void;
2245+
onSuggestionLeave?: () => void;
22392246
}) {
2247+
const router = useRouter();
2248+
const { setActivePanelSection } = useWorkspaceStore();
2249+
const { user } = useAuthStore();
22402250
const resolvedLogoUrl = logoUrl ? getLogoUrl(logoUrl) : undefined;
2251+
2252+
const firstName = user?.name?.split(' ')[0];
2253+
const greeting = firstName ? `Hello, ${firstName}.` : 'Hello.';
22412254
return (
2242-
<div className="flex h-full flex-col items-center justify-center">
2243-
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded-2xl bg-workspace-accent-10 overflow-hidden">
2255+
<div className="flex h-full flex-col items-center justify-center px-4">
2256+
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-workspace-accent-10 overflow-hidden">
22442257
{resolvedLogoUrl ? (
22452258
// eslint-disable-next-line @next/next/no-img-element
22462259
<img
@@ -2249,28 +2262,72 @@ function EmptyState({
22492262
className="h-full w-full object-contain p-1"
22502263
/>
22512264
) : (
2252-
<Bot size={32} className="text-workspace-accent" />
2265+
<Bot size={24} className="text-workspace-accent" />
22532266
)}
22542267
</div>
2255-
<p className="mb-8 max-w-md text-center text-muted-foreground">
2256-
Start a conversation with {selectedAgentName}. Ask questions, explore your data, or get
2257-
help with tasks.
2268+
<p className="mb-6 text-center text-muted-foreground">
2269+
{greeting} Pick a suggestion or type a message to get started.
22582270
</p>
22592271
{Array.isArray(suggestions) && suggestions.length > 0 && (
2260-
<div className="grid w-full max-w-xl grid-cols-2 gap-3">
2261-
{suggestions.map((suggestion, index) => {
2262-
const isOddLastItem = suggestions.length % 2 === 1 && index === suggestions.length - 1;
2272+
<div className="flex w-full max-w-lg flex-col gap-1.5">
2273+
{suggestions.map((suggestion) => {
2274+
const baseClass = cn(
2275+
'glass-card flex min-w-0 items-center justify-between px-4 py-2.5 text-left transition-all',
2276+
suggestion.disabled
2277+
? 'opacity-40 cursor-not-allowed'
2278+
: 'hover:border-primary/30 hover:glow-primary-sm cursor-pointer'
2279+
);
2280+
2281+
const content = (
2282+
<>
2283+
<div className="min-w-0 flex-1">
2284+
<span className="block truncate text-sm font-medium leading-tight">{suggestion.label}</span>
2285+
{suggestion.description && (
2286+
<span className="block truncate text-xs text-muted-foreground leading-snug">
2287+
{suggestion.description}
2288+
</span>
2289+
)}
2290+
{suggestion.disabled && (
2291+
<span className="block text-xs text-muted-foreground/60 italic">
2292+
Coming soon
2293+
</span>
2294+
)}
2295+
</div>
2296+
{!suggestion.disabled && (
2297+
<span className="ml-3 shrink-0 text-muted-foreground/40"></span>
2298+
)}
2299+
</>
2300+
);
2301+
2302+
if (suggestion.cta && !suggestion.disabled) {
2303+
const sectionId = suggestion.cta.replace(/^\//, '') as Parameters<typeof setActivePanelSection>[0];
2304+
return (
2305+
<button
2306+
key={`${suggestion.label}:${suggestion.value}`}
2307+
onMouseEnter={() => onSuggestionHover?.(suggestion.label)}
2308+
onMouseLeave={() => onSuggestionLeave?.()}
2309+
onClick={() => {
2310+
setActivePanelSection(sectionId);
2311+
router.push(suggestion.cta!);
2312+
}}
2313+
className={baseClass}
2314+
>
2315+
{content}
2316+
</button>
2317+
);
2318+
}
2319+
22632320
return (
2264-
<button
2265-
key={`${suggestion.label}:${suggestion.value}`}
2266-
onClick={() => onSuggestionClick(suggestion.value)}
2267-
className={cn(
2268-
'glass-card p-4 text-center text-sm transition-all hover:border-primary/30 hover:glow-primary-sm',
2269-
isOddLastItem && 'col-span-2 w-full max-w-[calc(50%-0.375rem)] justify-self-center'
2270-
)}
2271-
>
2272-
{suggestion.label}
2273-
</button>
2321+
<button
2322+
key={`${suggestion.label}:${suggestion.value}`}
2323+
onMouseEnter={() => !suggestion.disabled && onSuggestionHover?.(suggestion.value)}
2324+
onMouseLeave={() => onSuggestionLeave?.()}
2325+
onClick={() => !suggestion.disabled && onSuggestionClick(suggestion.value)}
2326+
disabled={suggestion.disabled}
2327+
className={baseClass}
2328+
>
2329+
{content}
2330+
</button>
22742331
);
22752332
})}
22762333
</div>

libs/naas-abi/naas_abi/apps/nexus/apps/web/src/components/shell/sidebar/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const SECTIONS: SectionDef[] = [
2929
{ id: 'graph', icon: <Waypoints size={18} />, label: 'Knowledge Graph', href: '/graph', feature: 'knowledge' },
3030
{ id: 'files', icon: <Folder size={18} />, label: 'Files', href: '/files', feature: 'files' },
3131
{ id: 'lab', icon: <FlaskConical size={18} />, label: 'Lab', href: '/lab', feature: 'agents' },
32-
{ id: 'apps', icon: <LayoutGrid size={18} />, label: 'App', href: '/apps', feature: 'agents' },
32+
{ id: 'apps', icon: <LayoutGrid size={18} />, label: 'Apps', href: '/apps', feature: 'agents' },
3333
];
3434

3535
export function Sidebar() {

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)